From 3dd380620d136ddf136fbf50a281f2a755070244 Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:21:19 +0530 Subject: [PATCH 1/9] Fix wrong Figma, Slack, Discord, and Google Sheets shortcuts Co-Authored-By: Claude Opus 5.5 --- lib/shortcuts/apps/discord.ts | 10 +++++----- lib/shortcuts/apps/figma.ts | 19 ++++++++++++++----- lib/shortcuts/apps/google-sheets.ts | 6 +++--- lib/shortcuts/apps/slack.ts | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/shortcuts/apps/discord.ts b/lib/shortcuts/apps/discord.ts index 8b7beccf..e7af7f9c 100644 --- a/lib/shortcuts/apps/discord.ts +++ b/lib/shortcuts/apps/discord.ts @@ -1,19 +1,19 @@ import type { AppShortcuts, Shortcut, ShortcutKey } from "../types.ts"; -const key = (platform: "windows" | "macos" | "linux", keys: string[], alternatives?: string[][]): ShortcutKey => ({ platform, keys, ...(alternatives ? { alternatives } : {}) }); -const pair = (id: string, action: string, win: string[], mac: string[], winAlt?: string[][], macAlt?: string[][]): Shortcut => ({ id, action, keys: [key("windows", win, winAlt), key("macos", mac, macAlt), key("linux", win, winAlt)] }); +const key = (platform: "windows" | "macos", keys: string[], alternatives?: string[][]): ShortcutKey => ({ platform, keys, ...(alternatives ? { alternatives } : {}) }); +const pair = (id: string, action: string, win: string[], mac: string[], winAlt?: string[][], macAlt?: string[][]): Shortcut => ({ id, action, keys: [key("windows", win, winAlt), key("macos", mac, macAlt)] }); const shared = (id: string, action: string, keys: string[], alternatives?: string[][]): Shortcut => pair(id, action, keys, keys, alternatives, alternatives); export const discord: AppShortcuts = { slug: "discord", name: "Discord", category: "Communication", - platforms: ["windows", "macos", "linux"], + platforms: ["windows", "macos"], versionContext: - "Current Discord commands, shortcuts, and navigation guide updated April 14, 2025 and checked October 10, 2026. The documented shortcuts apply to browser and desktop Discord, not the mobile app. The guide labels Windows and Mac bindings; Linux uses the Windows modifier column.", + "Current Discord commands, shortcuts, and navigation guide updated April 14, 2025 and checked November 1, 2026. The documented shortcuts apply to browser and desktop Discord, not the mobile app. The guide labels Windows and Mac bindings; Linux shortcuts are not listed.", sourceUrl: "https://support.discord.com/hc/en-us/articles/31232432266647-Discord-Commands-Shortcuts-and-Navigation-Guide", sourceLabel: "Discord commands, shortcuts, and navigation guide", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/figma.ts b/lib/shortcuts/apps/figma.ts index d31b48c0..ccab5665 100644 --- a/lib/shortcuts/apps/figma.ts +++ b/lib/shortcuts/apps/figma.ts @@ -63,9 +63,13 @@ export const figma: AppShortcuts = { { id: "comment", action: "Comment", keys: combo(platforms, ["C"]) }, { id: "eyedropper", - action: "Pick a color from anywhere", - keys: combo(platforms, ["Ctrl", "C"], ["Cmd", "C"]), - note: "Hold it to sample while dragging; works outside the canvas too.", + action: "Pick a color with the eyedropper", + keys: platforms.map((platform) => + platform === "macos" + ? { platform, keys: ["Control", "C"], alternatives: [["I"]] } + : { platform, keys: ["I"] }, + ), + note: "Sampling outside the canvas works only in the Figma desktop app on macOS.", }, { id: "focus-toolbar", @@ -158,10 +162,15 @@ export const figma: AppShortcuts = { intro: "Select through layers, move objects, change the canvas view, and keep the right panels in reach.", shortcuts: [ { - id: "show-layers", - action: "Show or hide the layers panel", + id: "add-auto-layout", + action: "Add auto layout to the selection", keys: combo(platforms, ["Shift", "A"]), }, + { + id: "remove-auto-layout", + action: "Remove auto layout", + keys: combo(platforms, ["Alt", "Shift", "A"], ["Option", "Shift", "A"]), + }, { id: "zoom-to-fit", action: "Zoom to fit everything", diff --git a/lib/shortcuts/apps/google-sheets.ts b/lib/shortcuts/apps/google-sheets.ts index d116d45a..2bd1bff0 100644 --- a/lib/shortcuts/apps/google-sheets.ts +++ b/lib/shortcuts/apps/google-sheets.ts @@ -1,7 +1,7 @@ import { combo, perPlatform } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; export const googleSheets: AppShortcuts = { slug: "google-sheets", @@ -9,7 +9,7 @@ export const googleSheets: AppShortcuts = { category: "Spreadsheet", platforms: [...platforms], versionContext: - "Google's official Google Sheets Help page checked in August 2026. Google labels Windows and ChromeOS as PC; Linux browsers generally use the same PC Ctrl and Alt bindings, while browser language, keyboard layout, and compatible spreadsheet shortcuts can change behavior.", + "Google's official Google Sheets Help page checked in August 2026. Google labels Windows and ChromeOS as PC and documents Mac separately. Browser language, keyboard layout, and compatible spreadsheet shortcuts can change behavior.", sourceUrl: "https://support.google.com/docs/answer/181110", sourceLabel: "official Google Sheets keyboard shortcuts", lastVerified: "2026-08-26", @@ -17,7 +17,7 @@ export const googleSheets: AppShortcuts = { { id: "common-actions", title: "Common actions", - intro: "Select, edit, save, print, copy, paste, and find in the grid with the bindings Google documents for PC, Mac, and Linux browsers.", + intro: "Select, edit, save, print, copy, paste, and find in the grid with the bindings Google documents for PC and Mac.", shortcuts: [ { id: "select-column", action: "Select the current column", keys: perPlatform(platforms, { windows: ["Ctrl", "Space"], macos: ["Ctrl", "Space"], linux: ["Ctrl", "Space"] }) }, { id: "select-row", action: "Select the current row", keys: combo(platforms, ["Shift", "Space"], ["Shift", "Space"]) }, diff --git a/lib/shortcuts/apps/slack.ts b/lib/shortcuts/apps/slack.ts index 85545a40..6f5323ad 100644 --- a/lib/shortcuts/apps/slack.ts +++ b/lib/shortcuts/apps/slack.ts @@ -67,7 +67,7 @@ export const slack: AppShortcuts = { { id: "threads-view", action: "Open the Threads view", keys: combo(platforms, ["Ctrl", "Shift", "T"], ["Cmd", "Shift", "T"]), note: "Desktop app only." }, { id: "channels-view", action: "Browse channels", keys: combo(platforms, ["Ctrl", "Shift", "L"], ["Cmd", "Shift", "L"]), note: "Desktop app only." }, { id: "conversation-details", action: "Open conversation details", keys: combo(platforms, ["Ctrl", "Shift", "I"], ["Cmd", "Shift", "I"]) }, - { id: "quick-switcher", action: "Jump to a conversation", keys: combo(platforms, ["Ctrl", "K", "Ctrl", "T"]), note: "Windows and Linux desktop apps." }, + { id: "quick-switcher", action: "Jump to a conversation", keys: perPlatform(platforms, { windows: ["Ctrl", "K"], linux: ["Ctrl", "K"] }).map((entry) => ({ ...entry, alternatives: [["Ctrl", "T"]] })), note: "Ctrl+T works only in the desktop app." }, { id: "focus-next-section", action: "Move focus to the next section", keys: combo(platforms, ["F6"]) }, { id: "focus-previous-section", action: "Move focus to the previous section", keys: combo(platforms, ["Shift", "F6"]) }, { id: "collapse-sidebar-sections", action: "Expand or collapse all sidebar sections", keys: perPlatform(platforms, { windows: ["Alt", "Click"], macos: ["Option", "Click"], linux: ["Alt", "Click"] }) }, From ed92faa7299f60319804533cb287de17eb18f298 Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:21:20 +0530 Subject: [PATCH 2/9] Warn that the docker group grants root access Co-Authored-By: Claude Opus 5.5 --- .../install-docker/page.tsx | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/app/(site)/(courses)/self-hosting-for-beginners/install-docker/page.tsx b/app/(site)/(courses)/self-hosting-for-beginners/install-docker/page.tsx index 87a9e5cd..4798806d 100644 --- a/app/(site)/(courses)/self-hosting-for-beginners/install-docker/page.tsx +++ b/app/(site)/(courses)/self-hosting-for-beginners/install-docker/page.tsx @@ -63,9 +63,34 @@ export default async function Page() { sudo usermod -aG docker $USER + +

+ Docker's own docs say the docker group{" "} + + grants root-level privileges + + . Anyone who logs in as your user, or any script running as you, can take full control + of the server through Docker. Only add an account you'd trust with sudo{" "} + anyway. +

+

+ If you'd rather not give that power away, Docker can run without root. It's called{" "} + + rootless mode + + , and it takes a few more setup steps. For a home server that only you log in to, the + docker group is the usual choice. +

+
+

- Log out and back in for that to take effect. Then check the whole thing works by running - a tiny test container: + Log out and back in for that to take effect. If you're on a virtual machine and it still + doesn't work, restart the machine. Then check the whole thing works by running a tiny + test container:

docker run hello-world From a30bb974446cc9bb53c92115b06a34d5f5cb2b4f Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:21:20 +0530 Subject: [PATCH 3/9] Lead the digital products fee page with Gumroad vs Payhip vs Etsy Co-Authored-By: Claude Opus 5.5 --- .../comparison.mdx | 27 +++++++++++++++++++ .../platform-fees/[section]/content/index.ts | 14 +++++++--- .../platform-fees/[section]/page.tsx | 3 ++- .../platform-fees/components.tsx | 5 +++- 4 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 app/(site)/(directories)/platform-fees/[section]/content/digital-products-and-stores/comparison.mdx diff --git a/app/(site)/(directories)/platform-fees/[section]/content/digital-products-and-stores/comparison.mdx b/app/(site)/(directories)/platform-fees/[section]/content/digital-products-and-stores/comparison.mdx new file mode 100644 index 00000000..3ec4f490 --- /dev/null +++ b/app/(site)/(directories)/platform-fees/[section]/content/digital-products-and-stores/comparison.mdx @@ -0,0 +1,27 @@ +## Gumroad vs Payhip vs Etsy: what you keep from one sale + +Payhip's free plan leaves you the most at every price below, and Gumroad leaves you the least. Gumroad's fixed 50¢ fee hurts most on cheap products. + + + +| Price | Gumroad | Payhip free plan | Etsy | +| --- | --- | --- | --- | +| $5 | $3.55 | $4.30 | $4.07 | +| $20 | $16.62 | $18.12 | $17.65 | +| $100 | $86.30 | $91.80 | $90.05 | + +Gumroad takes 10% plus 50¢, then card processing takes 2.9% plus 30¢. Payhip takes 5%, then Stripe takes its standard 2.9% plus 30¢. Etsy takes a 6.5% transaction fee, 3% plus 25¢ for payment processing, and the 20¢ listing fee. Totals are rounded to the cent. + + + +These numbers are for a sale you bring in yourself. Each platform charges differently when it finds the buyer for you: + +- **Gumroad Discover:** 30% of the sale, card fees included. +- **Etsy Offsite Ads:** 15% of the order, or 12% once you pass $10,000 in yearly sales, up to $100 per order. You pay it only when a sale comes from one of Etsy's ads. +- **Payhip marketplace:** no extra fee on top of your plan's fee. + +Payhip's paid plans cut the per-sale fee. Plus costs $29 a month and takes 2%, so it beats the free plan once you sell more than about $967 a month. Pro costs $99 a month and takes nothing, so it beats Plus above $3,500 a month. Etsy may also charge a one-time fee to open a shop, depending on where you live. + +All three collect and pay US sales tax for you. Gumroad is the merchant of record, so it handles sales tax and VAT. Payhip has handled US sales tax since 1 July 2026, and it also handles EU and UK VAT on digital products. Etsy collects US sales tax where state law requires it, and VAT on digital items in the countries it lists. + +Sources: [Gumroad pricing](https://gumroad.com/pricing), [Gumroad fees](https://gumroad.com/help/article/66-gumroads-fees), [Payhip pricing](https://payhip.com/pricing), [Payhip taxes](https://payhip.com/features/vat-taxes), [Etsy fees](https://help.etsy.com/hc/en-us/articles/115014483627-What-are-the-Fees-and-Taxes-for-Selling-on-Etsy), [Etsy payment processing](https://help.etsy.com/hc/en-us/articles/115015628847-What-are-Payment-Processing-Fees-for-Selling-on-Etsy), and [Etsy Offsite Ads](https://help.etsy.com/hc/en-us/articles/360000338367-How-Etsy-s-Offsite-Ads-Work). diff --git a/app/(site)/(directories)/platform-fees/[section]/content/index.ts b/app/(site)/(directories)/platform-fees/[section]/content/index.ts index 432cd58c..e67e929b 100644 --- a/app/(site)/(directories)/platform-fees/[section]/content/index.ts +++ b/app/(site)/(directories)/platform-fees/[section]/content/index.ts @@ -6,6 +6,7 @@ import CreativeAssetsIntro from "./creative-assets/intro.mdx"; import CreatorsAndCommunitiesArticle from "./creators-and-communities/article.mdx"; import CreatorsAndCommunitiesIntro from "./creators-and-communities/intro.mdx"; import DigitalProductsAndStoresArticle from "./digital-products-and-stores/article.mdx"; +import DigitalProductsAndStoresComparison from "./digital-products-and-stores/comparison.mdx"; import DigitalProductsAndStoresIntro from "./digital-products-and-stores/intro.mdx"; import MusicAndAudioArticle from "./music-and-audio/article.mdx"; import MusicAndAudioIntro from "./music-and-audio/intro.mdx"; @@ -16,12 +17,19 @@ import TeachingAndCoursesIntro from "./teaching-and-courses/intro.mdx"; import WritingAndBooksArticle from "./writing-and-books/article.mdx"; import WritingAndBooksIntro from "./writing-and-books/intro.mdx"; -/** Each fee category's own words, keyed by its slug. Add an entry when a category is added. */ -export const feeCategoryContent: Record = { +/** + * Each fee category's own words, keyed by its slug. Add an entry when a category is added. + * `Lead` renders above the listing, for an answer the page title promises. + */ +export const feeCategoryContent: Record = { "apps-and-software": { Intro: AppsAndSoftwareIntro, Article: AppsAndSoftwareArticle }, "creative-assets": { Intro: CreativeAssetsIntro, Article: CreativeAssetsArticle }, "creators-and-communities": { Intro: CreatorsAndCommunitiesIntro, Article: CreatorsAndCommunitiesArticle }, - "digital-products-and-stores": { Intro: DigitalProductsAndStoresIntro, Article: DigitalProductsAndStoresArticle }, + "digital-products-and-stores": { + Intro: DigitalProductsAndStoresIntro, + Lead: DigitalProductsAndStoresComparison, + Article: DigitalProductsAndStoresArticle, + }, "music-and-audio": { Intro: MusicAndAudioIntro, Article: MusicAndAudioArticle }, "payments": { Intro: PaymentsIntro, Article: PaymentsArticle }, "teaching-and-courses": { Intro: TeachingAndCoursesIntro, Article: TeachingAndCoursesArticle }, diff --git a/app/(site)/(directories)/platform-fees/[section]/page.tsx b/app/(site)/(directories)/platform-fees/[section]/page.tsx index e3b554ad..07c74855 100644 --- a/app/(site)/(directories)/platform-fees/[section]/page.tsx +++ b/app/(site)/(directories)/platform-fees/[section]/page.tsx @@ -52,10 +52,11 @@ export default async function FeeCategoryPage({ params }: { params: Promise<{ se }; }); - const { Intro, Article } = content; + const { Intro, Lead, Article } = content; return } + lead={Lead ? : undefined} article={Article ?
: undefined} group={group} anchors={blocks.map(block => ({ diff --git a/app/(site)/(directories)/platform-fees/components.tsx b/app/(site)/(directories)/platform-fees/components.tsx index 1bc0418c..657f8ab2 100644 --- a/app/(site)/(directories)/platform-fees/components.tsx +++ b/app/(site)/(directories)/platform-fees/components.tsx @@ -41,9 +41,11 @@ export function SectionIcon({ icon, size = 22 }: { icon: FeeSection['icon']; siz } /** `intro` and `article` are the page's own MDX content. */ -export async function FeeShell({ directoryPage, intro, article, children, group, anchors = [] }: { +export async function FeeShell({ directoryPage, intro, lead, article, children, group, anchors = [] }: { directoryPage: Stack127DirectoryPage; intro: ReactNode; + /** Content shown above the listing. */ + lead?: ReactNode; article?: ReactNode; children: ReactNode; group?: FeeGroup; @@ -73,6 +75,7 @@ export async function FeeShell({ directoryPage, intro, article, children, group, + {lead ?
{lead}
: null} {children} {article ?
{article}
: null}
From 6ee073fd0eec70844d5ddd60e38035890e34f0d0 Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:21:20 +0530 Subject: [PATCH 4/9] Rewrite raw research notes on ad-network and fee records in plain words Co-Authored-By: Claude Opus 5.5 --- .../ad-networks/network-facts.ts | 86 ++-- scripts/platform-fees/data/directory.json | 384 +++++++++--------- 2 files changed, 235 insertions(+), 235 deletions(-) diff --git a/app/(site)/(directories)/ad-networks/network-facts.ts b/app/(site)/(directories)/ad-networks/network-facts.ts index e5deb7c6..0dd34457 100644 --- a/app/(site)/(directories)/ad-networks/network-facts.ts +++ b/app/(site)/(directories)/ad-networks/network-facts.ts @@ -29,48 +29,48 @@ export type AdNetworkFacts = { // Keyed by network slug. export const adNetworkFacts: Record = { - "adapex": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 60","note":"Monthly net terms; no public threshold or share.","sourceUrl":"https://adapex.io/faqs/"}],"joining":[{"label":"Application inputs","value":"Publisher form asks monthly revenue, monthly pageviews, and USA traffic share","note":"These are intake fields, not a published cutoff.","sourceUrl":"https://adapex.io/get-in-touch/"},{"label":"Traffic","value":"No numeric minimum published; positioned for mid-sized publishers","sourceUrl":"https://adapex.io/faqs/"},{"label":"Must connect","value":"Google Ad Manager through MCM","note":"Adapex says it works with existing AdSense, AdX and Prebid.","sourceUrl":"https://adapex.io/faqs/"},{"label":"Contract","value":"No long-term contract required to start; longer terms can be offered","sourceUrl":"https://adapex.io/faqs/"}],"revenueShare":null,"paymentTerms":"Net 60; paid monthly through the publisher's established method","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher FAQ","url":"https://adapex.io/faqs/"},{"label":"Contact","url":"https://adapex.io/get-in-touch/"}],"minTraffic":null}, - "aditude": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Application","value":"Book a demo/contact sales; no self-serve gate found.","note":"Web/app platform with tailored service.","sourceUrl":"https://www.aditude.com/contact"},{"label":"Traffic","value":"No public numeric requirement found.","sourceUrl":"https://www.aditude.com/case-studies"},{"label":"Publisher types","value":"Case studies cover media, gaming, audio and apps.","sourceUrl":"https://www.aditude.com/case-studies"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Aditude publisher guidance","url":"https://www.aditude.com/blog/the-5-questions-every-publisher-should-ask-their-monetization-partner"},{"label":"Publisher platform","url":"https://www.aditude.com/"},{"label":"Website terms","url":"https://www.aditude.com/policies/terms"},{"label":"Case studies","url":"https://www.aditude.com/case-studies"},{"label":"Dexerto case study","url":"https://www.aditude.com/case-studies/dexerto"},{"label":"Publisher contact","url":"https://www.aditude.com/contact"}],"minTraffic":null}, - "adnimation": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 35","note":"Standard managed service; MAP has separate terms.","sourceUrl":"https://www.adnimation.com/faqs/"}],"joining":[{"label":"Standard traffic","value":"Substantial; no official number.","note":"CEO described 1–20m monthly visitors as a historical sweet spot, not a cutoff.","sourceUrl":"https://www.adnimation.com/adnimation-co-founder-talks-plans-for-growth-with-mergermarket/"},{"label":"MAP traffic","value":"No PV minimum; all sites reviewed.","sourceUrl":"https://www.adnimation.com/map/"},{"label":"Content","value":"No gambling, pornography, drugs or similar content.","sourceUrl":"https://www.adnimation.com/faqs/"},{"label":"Payment methods","value":"US ACH; international wire; PayPal by arrangement.","sourceUrl":"https://www.adnimation.com/faqs/"},{"label":"Google standing","value":"Follow relevant Google policy guidelines","sourceUrl":"https://www.adnimation.com/faqs/"},{"label":"Must connect","value":"Share analytics access and connect Google Ad Manager, or have one created; install the ad code","sourceUrl":"https://www.adnimation.com/faqs/"}],"revenueShare":"Revenue-share model; percentage not public","paymentTerms":"Net 35 after month end","paymentThreshold":null,"paymentMethods":["ACH (US)","International wire","PayPal by arrangement"],"sources":[{"label":"Publisher FAQ","url":"https://www.adnimation.com/faqs/"},{"label":"MAP program","url":"https://www.adnimation.com/map/"},{"label":"MAP terms","url":"https://www.adnimation.com/map-terms-conditions/"},{"label":"Co-founder interview","url":"https://www.adnimation.com/adnimation-co-founder-talks-plans-for-growth-with-mergermarket/"},{"label":"Case study","url":"https://www.adnimation.com/case-study/"}],"minTraffic":null}, - "adpushup": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Vendor-published revenue signal","value":"$3,000/month","note":"Soft signal in vendor small-publisher article; current onboarding does not confirm it as a hard gate.","sourceUrl":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"},{"label":"Payment minimum","value":"$50","note":"Publisher terms last revised April 20, 2021; verify current offer.","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"},{"label":"Pay day","value":"Net 45","note":"2021-revised terms; verify current offer.","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"}],"joining":[{"label":"Traffic","value":"No confirmed current minimum.","note":"Vendor article says no PV minimum; onboarding page is silent.","sourceUrl":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"},{"label":"Revenue","value":"About $3k/month is cited in vendor eligibility article.","note":"Not confirmed by current application route.","sourceUrl":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"},{"label":"Application","value":"Submit site for review and install approved code.","sourceUrl":"https://www.adpushup.com/publisher/get-started/"},{"label":"Content","value":"No illegal, pornographic, hateful, fraudulent, or infringing content; no invalid activity","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"},{"label":"Must connect","value":"Install AdPushup code on approved sites","note":"AdPushup can approve or reject a site.","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"},{"label":"Ad revenue","value":"$3,000 monthly in a vendor article","note":"Unconfirmed by current onboarding.","sourceUrl":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"}],"revenueShare":"Separate publisher agreement sets share; not public","paymentTerms":"Net 45 under 2021 terms","paymentThreshold":"$50 under 2021 terms","paymentMethods":["Direct deposit","Wire transfer","PayPal"],"sources":[{"label":"Small-publisher article","url":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"},{"label":"Terms (revised 2021)","url":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"},{"label":"Onboarding","url":"https://www.adpushup.com/publisher/get-started/"}],"minTraffic":null}, - "amazon-publisher-services": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"UAM fee","value":"10%","note":"Stated in 2023 FAQ; verify because current page does not repeat the rate.","sourceUrl":"https://aps.amazon.com/aps/wp-content/uploads/2023/01/aps_uam_update_011123-FINAL.pdf"},{"label":"Payment minimum","value":"$5 USD","note":"Current UAM product page confirms.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"},{"label":"Pay day","value":"Net 60","note":"Current UAM product page confirms.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"}],"joining":[{"label":"Application","value":"Invitation only; approved invite code and site.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"},{"label":"Must connect","value":"Google Ad Manager; publisher directly or exclusively represents the site.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"},{"label":"Geography","value":"Worldwide availability; bidder demand varies by country.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"}],"revenueShare":null,"paymentTerms":"Net 60","paymentThreshold":"$5 USD","paymentMethods":[],"sources":[{"label":"UAM FAQ / fee terms","url":"https://aps.amazon.com/aps/wp-content/uploads/2023/01/aps_uam_update_011123-FINAL.pdf"},{"label":"Current UAM page","url":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"}],"minTraffic":null}, - "anymind-anymanager": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Product fit","value":"Web/app monetization with 150+ ad-serving partners, SDK, video and PMP.","sourceUrl":"https://anymindgroup.com/products/anymanager/"},{"label":"Application","value":"Contact AnyMind; no self-serve entry gate found.","sourceUrl":"https://anymindgroup.com/products/anymanager/"},{"label":"Regional fit","value":"APAC is the clearest published focus; broader coverage also offered.","sourceUrl":"https://anymindgroup.com/case-study/?business=publishergrowth&pa=2"},{"label":"Must connect","value":"AnyManager tag for web or SDK for apps","sourceUrl":"https://anymindgroup.com/news/press-release/anymanagergames-launch/"}],"revenueShare":"Fixed percentage agreed per publisher; not public","paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"AnyMind IR FAQ","url":"https://anymindgroup.com/ir/faq/"},{"label":"AnyManager overview","url":"https://anymindgroup.com/news/press-release/11388/"},{"label":"AnyManager Games launch and SDK","url":"https://anymindgroup.com/news/press-release/anymanagergames-launch/"},{"label":"AnyManager AI reporting and integration overview","url":"https://anymindgroup.com/news/press-release/anymanager-ai-reporting-agent"},{"label":"AnyManager product","url":"https://anymindgroup.com/products/anymanager/"},{"label":"Publisher cases","url":"https://anymindgroup.com/case-study/?business=publishergrowth&pa=2"},{"label":"AnyManager terms","url":"https://anymanager.io/terms-of-service/"}],"minTraffic":null}, - "buysellads-carbon-ads": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Withdrawal balance","value":"$1","note":"Method minimums: $20 PayPal, $50 check, $500 wire; fees may apply.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Earnings timing","value":"30 / 60 days","note":"Posted within 30 days of month-end; withdrawable within 60 days.","sourceUrl":"https://www.carbonads.net/faq"}],"joining":[{"label":"Application","value":"Invite-only; review usually 5–7 business days.","note":"Reviews traffic, relevance, maintenance and capacity.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Audience","value":"English-speaking developer/design audience; US, Canada, Europe strongest.","note":"Other areas depend on demand.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Exclusivity","value":"Carbon must be the only ads on the site; no backfill.","note":"Replacement, not add-on.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Traffic","value":"Monthly pageviews reviewed; no fixed minimum.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Visitor location","value":"Strongest demand is for English-speaking developer and design audiences in the US, Canada, and Europe; other regions depend on demand","note":"This is a demand profile, not an exclusive country list.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Content","value":"Sites are reviewed for ad relevance and whether they are actively maintained","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Must connect","value":"Use the Carbon Tag or API","sourceUrl":"https://www.buysellads.com/publishers"},{"label":"Placement","value":"Exclusive placement","sourceUrl":"https://www.carbonads.net/faq"}],"revenueShare":null,"paymentTerms":"Earnings posted monthly within 30 days; withdrawal within 60 days","paymentThreshold":"$1 before method minimums","paymentMethods":["PayPal","Check","Wire transfer","Open Collective, quarterly by request"],"sources":[{"label":"FAQ","url":"https://www.carbonads.net/faq"},{"label":"Join","url":"https://www.carbonads.net/join"},{"label":"Placement policy","url":"https://www.carbonads.net/placement-policy"},{"label":"Carbon API guide","url":"https://www.carbonads.net/docs"},{"label":"BuySellAds publisher page","url":"https://www.buysellads.com/publishers"},{"label":"About","url":"https://www.carbonads.net/about"}],"minTraffic":null}, - "clickio": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Indicative traffic guide","value":"100,000–1,000,000 monthly pageviews","note":"Country-specific Fully Managed guide; not guaranteed approval.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Below-guide route","value":">$500 monthly ad revenue","note":"May require proof for 3 consecutive months.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Pay day","value":"Net 60","note":"Some demand partners may offer Net30.","sourceUrl":"https://docs.clickio.com/books/get-started/page/payments-billing"}],"joining":[{"label":"Traffic","value":"100k PV UK/France/US/Australia/Canada; 250k Italy/Spain; 300k rest Europe; 500k Portugal; 750k Brazil/Colombia/Mexico; 1m rest LatAm/world.","note":"Indicative guide.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Revenue alternative","value":">$500 monthly ad revenue below regional guide.","note":"May require proof for 3 consecutive months.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Verification","value":"Six months of GA reports may be used if other verification unavailable.","note":"Evidence window, not domain-age rule.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Technical","value":"ads.txt, IAB-approved CMP, >50% viewability; follow AdSense policy.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Visitor location","value":"Sanctioned countries excluded","note":"Traffic thresholds vary by region.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Content","value":"Follow AdSense policies and third-party rights rules","note":"Adult, shocking, or racially intolerant content cannot be monetized.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Analytics history","value":"Six months of analytics history may be requested","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards","note":"Reporting evidence, not a site-age minimum."},{"label":"Ad revenue","value":"More than $500 monthly ad revenue","note":"Below the traffic guide; verification may require at least $500 in each of three consecutive months.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"}],"joinSummary":"Country guide varies; below-guide route is >$500 monthly ad revenue, potentially verified across three months","revenueShare":"Variable by demand-partner tariff","paymentTerms":"Net 60; some demand partners Net30","paymentThreshold":"$100 ACH/eCheck/SEPA/check/PayPal; $200 wire; $10 WebMoney; GBP100 Payoneer","paymentMethods":["ACH","eCheck","SEPA","Wire","Check","PayPal","WebMoney","Payoneer"],"sources":[{"label":"Entry standards","url":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Payments","url":"https://docs.clickio.com/books/get-started/page/payments-billing"},{"label":"Publisher agreement","url":"https://legal.clickio.com/PublisherAgreement/"},{"label":"How to join Clickio","url":"https://docs.clickio.com/books/get-started/chapter/how-to-join-clickio"}],"minTraffic":null}, - "concert": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Product","value":"Publisher-led curated SSP for premium publisher inventory.","sourceUrl":"https://www.voxmedia.com/2022/6/7/23157157/vox-media-concert-ssp-publisher-led-supply-side-platform-trade-desk-as-an-exclusive-launch-partner/"},{"label":"Application","value":"No public publisher form or qualification page found.","note":"Available terms were advertiser-facing.","sourceUrl":"https://admanager.concert.io/terms"},{"label":"Publisher fit","value":"Launch examples are established media groups; examples are not an admission minimum.","sourceUrl":"https://www.voxmedia.com/about-vox-media/2018/5/31/17413008/top-digital-publishers-join-concert-to-create-largest-premium-advertising-marketplace-online/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"SSP launch","url":"https://www.voxmedia.com/2022/6/7/23157157/vox-media-concert-ssp-publisher-led-supply-side-platform-trade-desk-as-an-exclusive-launch-partner/"},{"label":"Advertiser terms","url":"https://admanager.concert.io/terms"},{"label":"Concert media kit","url":"https://cdn.vox-cdn.com/uploads/chorus_asset/file/22462899/Media_Kit_April_2021pdf.pdf"},{"label":"Publisher group","url":"https://www.voxmedia.com/about-vox-media/2018/5/31/17413008/top-digital-publishers-join-concert-to-create-largest-premium-advertising-marketplace-online/"}],"minTraffic":null}, - "ethicalads": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Traffic","value":"50,000 monthly pageviews","note":"Exceptions may be considered for close/fast-growing high-quality developer sites.","sourceUrl":"https://www.ethicalads.io/publishers/faq/"},{"label":"Share","value":"70%","note":"Published publisher policy.","sourceUrl":"https://www.ethicalads.io/publisher-policy/"},{"label":"Payment minimum","value":"$50","note":"Monthly target by the 15th.","sourceUrl":"https://www.ethicalads.io/publisher-policy/"}],"joining":[{"label":"Traffic","value":"At least 50k PV/month; discretionary exception path for close-fit developer sites.","sourceUrl":"https://www.ethicalads.io/publishers/faq/"},{"label":"Audience","value":"Developer-focused: software, OSS, DevOps, security, data science.","sourceUrl":"https://www.ethicalads.io/publisher-guide/"},{"label":"Placement","value":"One EthicalAds placement per page; no simultaneous competing ad on that page.","sourceUrl":"https://www.ethicalads.io/publisher-policy/"},{"label":"Content","value":"Developer-focused sites that meet the publisher policy","note":"The policy sets ad placement and click-through requirements","sourceUrl":"https://www.ethicalads.io/publishers/faq/"},{"label":"Must connect","value":"Install the EthicalAds ad client after applying","sourceUrl":"https://www.ethicalads.io/publisher-guide/"}],"revenueShare":"70%","paymentTerms":"Monthly, target by 15th","paymentThreshold":"$50","paymentMethods":["PayPal","Open Collective","GitHub Sponsors","Stripe Connect/bank transfer"],"sources":[{"label":"FAQ","url":"https://www.ethicalads.io/publishers/faq/"},{"label":"Policy","url":"https://www.ethicalads.io/publisher-policy/"},{"label":"Guide","url":"https://www.ethicalads.io/publisher-guide/"},{"label":"Terms","url":"https://www.ethicalads.io/terms-of-service/"}],"minTraffic":{"amount":50000,"unit":"pageviews"},"joinSummary":"50,000 pageviews a month; close-fit developer sites may receive an exception"}, - "ezoic": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"New-site gate","value":"250,000 monthly active users","note":"MAU, not PV or sessions.","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Share","value":"At least 82%","note":"Net display-ad revenue unless separate agreement.","sourceUrl":"https://www.ezoic.com/terms"},{"label":"Payment minimum","value":"$20","note":"Published terms.","sourceUrl":"https://www.ezoic.com/terms"}],"joining":[{"label":"Traffic","value":"New sites generally need 250k MAU; do not convert to PV/sessions.","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Incubator","value":"Up to 20 sites/month; discretionary review based on growth, site value, backend quality and publisher profile.","sourceUrl":"https://support.ezoic.com/kb/article/ezoic-incubator-program"},{"label":"Grandfathering","value":"Sites monetizing before 2026-02-19 may retain access with continuous integration; >7-day removal can end status.","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Content/tech","value":"Original supported-language content, Google policies and JavaScript integration; AdSense approval generally not required.","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Google standing","value":"Comply with Google and AdSense policies; AdSense approval is not required for most sites","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Must connect","value":"Connect Google Analytics for traffic verification and Google MCM; integrate JavaScript","sourceUrl":"https://support.ezoic.com/kb/article/ezoic-site-requirements-for-monetization"},{"label":"Approval time","value":"Ezoic review usually finishes within 10 days; Google MCM review can take up to 18 days","sourceUrl":"https://support.ezoic.com/kb/article/ezoic-site-requirements-for-monetization"}],"revenueShare":"At least 82% of net display-ad revenue","paymentTerms":"About 35 days after advertiser payment; payment help also describes Net30 and optional longer cycles","paymentThreshold":"$20","paymentMethods":["Wise","PayPal","Payoneer","Check"],"sources":[{"label":"Requirements","url":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Site requirements and review timing","url":"https://support.ezoic.com/kb/article/ezoic-site-requirements-for-monetization"},{"label":"Terms","url":"https://www.ezoic.com/terms"},{"label":"Payment help","url":"https://support.ezoic.com/kb/article/ezoic-payments?lang=en-US"},{"label":"Incubator","url":"https://support.ezoic.com/kb/article/ezoic-incubator-program"},{"label":"Payment help","url":"https://support.ezoic.com/kb/article/ezoic-payment-schedule-and-thresholds"}],"minTraffic":{"amount":250000,"unit":"monthly active users","label":"250,000 monthly active users"},"joinSummary":"250k monthly active users for new sites; limited Incubator below gate"}, - "freestar": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Typical scale","value":"About 1m monthly pageviews","note":"Typical, not a hard floor.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Target-market share","value":"50%","note":"US/UK/Canada/NZ/Australia.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Organic audience","value":"50%","note":"Includes social, News/Discover, YouTube, Reddit and syndication—not only search.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"}],"joining":[{"label":"Traffic","value":"Typically about 1m PV/mo; all sizes can apply.","note":"Typical scale, not cutoff.","sourceUrl":"https://freestar.com/contact-us/"},{"label":"Geography","value":"At least half from US/UK/Canada/NZ/Australia.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Traffic source","value":"At least 50% from organic audiences; includes social/news/video/community/syndication.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Content","value":"Original, human-focused, mostly long-form, brand-safe; GA4 and history requested.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Google standing","value":"Good standing with all major ad providers","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Must connect","value":"Provide GA4 access, traffic and revenue history, and demand-partner status","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Approval time","value":"About 3 to 6 weeks from kickoff to launch","note":"This is onboarding-to-launch timing, not review time alone.","sourceUrl":"https://freestar.com/contact-us/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Quality requirements","url":"https://freestar.com/publisher-quality-requirements/"},{"label":"Contact/onboarding","url":"https://freestar.com/contact-us/"}],"minTraffic":null}, - "geniee-ssp": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Payment minimum","value":"$100 PayPal/Payoneer; $200 wire","note":"From earlier publisher terms; 2025 English terms did not reconfirm the threshold.","sourceUrl":"https://s.geniee.jp/aladdin/resources/GenieeInternational/terms_geniee_en.pdf"},{"label":"Payment timing","value":"Two months","note":"By the end of the second month after calculation; older published terms.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"}],"joining":[{"label":"Application","value":"Submit publisher signup and accurate site details.","sourceUrl":"https://admin.genieessp.com/app/signup/en_US"},{"label":"Traffic","value":"No public numeric floor located.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"},{"label":"Content","value":"No false, obscene, infringing or illegal material.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"},{"label":"Integration","value":"Install and retain Geniee tags.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"}],"revenueShare":null,"paymentTerms":"Current 2025 terms: transfer by end of second month after calculation, conditional on advertiser payment.","paymentThreshold":"Earlier terms: $100 PayPal/Payoneer; $200 wire; current 2025 terms do not reconfirm","paymentMethods":["Bank transfer (current 2025 terms)","PayPal (current 2025 terms)","Payoneer (listed in earlier terms; not reconfirmed)","Wire transfer (listed in earlier terms; not reconfirmed)"],"sources":[{"label":"English publisher terms, older","url":"https://s.geniee.jp/aladdin/resources/GenieeInternational/terms_geniee_en.pdf"},{"label":"Publisher signup","url":"https://admin.genieessp.com/app/signup/en_US"},{"label":"Publisher terms, 2025","url":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"},{"label":"SSP overview","url":"https://en.geniee.co.jp/services/publishers/"}],"minTraffic":null}, - "google-adsense": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Publisher share","value":"80%","note":"For AdSense for Content after advertiser platform fee; about 68% with Google Ads demand.","sourceUrl":"https://support.google.com/adsense/answer/180195?hl=en"},{"label":"Payment minimum","value":"$100 USD","note":"Equivalent thresholds vary by account currency.","sourceUrl":"https://support.google.com/adsense/answer/1709871?hl=en"},{"label":"Pay day","value":"21st–26th each month","note":"When balance reaches payment threshold and no holds.","sourceUrl":"https://support.google.com/adsense/answer/7164701?hl=en"}],"joining":[{"label":"Traffic","value":"No numeric traffic rule on current eligibility checklist.","note":"This page does not promise that no minimum exists.","sourceUrl":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Content","value":"Original, high-quality content and publisher-policy compliance.","sourceUrl":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Applicant","value":"18+, or adult representative; must own/control site HTML and be in a supported signup country.","sourceUrl":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Review","value":"Usually days; sometimes 2–4 weeks; finish setup within 6 months.","sourceUrl":"https://support.google.com/adsense/answer/7402256?hl=en-AG"},{"label":"Visitor location","value":"Sign-up is available in listed countries","sourceUrl":"https://support.google.com/adsense/answer/13402307?hl=en"},{"label":"Google standing","value":"No account termination for invalid traffic or policy violations","sourceUrl":"https://support.google.com/adsense/answer/81904?hl=en"},{"label":"Must connect","value":"Access to the site HTML","note":"Add the verification code or ad code and request a site review","sourceUrl":"https://support.google.com/adsense/answer/7584263?hl=en"}],"revenueShare":"80% after advertiser platform fee for AdSense for Content","paymentTerms":"Monthly; issued 21st–26th after threshold and holds clear","paymentThreshold":"$100 USD or account-currency equivalent","paymentMethods":["Check","EFT","SEPA","Wire","PayPal Hyperwallet where available"],"sources":[{"label":"Share","url":"https://support.google.com/adsense/answer/180195?hl=en"},{"label":"Payment threshold","url":"https://support.google.com/adsense/answer/1709871?hl=en"},{"label":"Payment schedule","url":"https://support.google.com/adsense/answer/7164701?hl=en"},{"label":"Eligibility","url":"https://support.google.com/adsense/answer/9724?hl=en"},{"label":"Signup countries","url":"https://support.google.com/adsense/answer/13402307?hl=en"},{"label":"Account restrictions after invalid traffic or policy termination","url":"https://support.google.com/adsense/answer/81904?hl=en"},{"label":"Site review timing","url":"https://support.google.com/adsense/answer/7402256?hl=en"},{"label":"Site verification and review steps","url":"https://support.google.com/adsense/answer/7584263?hl=en"},{"label":"Payment methods","url":"https://support.google.com/adsense/answer/1714397?hl=en-EN"},{"label":"Eligibility","url":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Activation and review","url":"https://support.google.com/adsense/answer/7402256?hl=en-AG"}],"minTraffic":null}, - "infolinks": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Traffic","value":"No minimum PV or visitor count","note":"Current official support result says all publisher sizes can apply.","sourceUrl":"https://sites0001.infolinks.com/support/quality/what-are-the-eligibility-criteria-for-my-site-to-qualify-with-infolinks"},{"label":"Payment minimum","value":"$50 / $100","note":"Most methods / wire or Western Union, respectively.","sourceUrl":"https://sites0001.infolinks.com/support/payments/what-are-my-payment-options"},{"label":"Pay timing","value":"45 days after month-end","note":"Service agreement says 45 working days; current help says 45 days after month-end—contract is older.","sourceUrl":"https://sites0001.infolinks.com/service-agreement"}],"joining":[{"label":"Traffic","value":"No PV/visitor minimum; every site reviewed and may be declined.","sourceUrl":"https://sites0001.infolinks.com/support/quality/what-are-the-eligibility-criteria-for-my-site-to-qualify-with-infolinks"},{"label":"Review","value":"Usually within 48 hours.","sourceUrl":"https://sites0001.infolinks.com/support/account/how-do-i-sign-up-as-an-infolinks-publisher"},{"label":"Content","value":"No illegal, adult/offensive, hateful or infringing content.","sourceUrl":"https://sites0001.infolinks.com/service-agreement"},{"label":"Integration","value":"Install Infolinks JavaScript after approval.","sourceUrl":"https://sites0001.infolinks.com/support/integration/how-do-i-integrate-infolinks-into-my-website"},{"label":"Google standing","value":"Publisher or related entities must not have been terminated by another monetization platform in the prior 24 months","note":"The agreement names Google AdSense as an example.","sourceUrl":"https://sites0001.infolinks.com/service-agreement"}],"revenueShare":"70% for In-Text ads under 2015 agreement; other format rates vary","paymentTerms":"45 working days after month-end under 2015 agreement","paymentThreshold":"$50 most methods; $100 wire/Western Union","paymentMethods":["PayPal","Bank wire","eCheck","ACH","Payoneer","Western Union"],"sources":[{"label":"Eligibility","url":"https://sites0001.infolinks.com/support/quality/what-are-the-eligibility-criteria-for-my-site-to-qualify-with-infolinks"},{"label":"Publisher agreement (2015)","url":"https://sites0001.infolinks.com/service-agreement"},{"label":"Signup","url":"https://sites0001.infolinks.com/support/account/how-do-i-sign-up-as-an-infolinks-publisher"},{"label":"Integration","url":"https://sites0001.infolinks.com/support/integration/how-do-i-integrate-infolinks-into-my-website"},{"label":"Payments","url":"https://sites0001.infolinks.com/support/payments/what-are-my-payment-options"},{"label":"Payment thresholds","url":"https://sites0001.infolinks.com/support/payments/what-is-the-infolinks-payment-threshold"},{"label":"Payment fees","url":"https://sites0001.infolinks.com/support/payments/do-i-pay-fees-when-receiving-my-infolinks-payment"}],"minTraffic":{"amount":0,"unit":"pageviews"}}, - "journey-by-mediavine": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Sessions to apply","value":"1,000 Tier 1 sessions in 30 days","note":"Current Mediavine application requirement for Journey.","sourceUrl":"https://www.mediavine.com/application-requirements/"},{"label":"Share","value":"70%","note":"Journey publisher share.","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"},{"label":"Pay day","value":"Net 65","note":"Journey schedule.","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"}],"joining":[{"label":"Traffic","value":"At least 1k Tier 1 sessions in 30 days.","sourceUrl":"https://www.mediavine.com/application-requirements/"},{"label":"Setup","value":"Run Grow for at least 30 days; GA4 is optional.","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"},{"label":"Site age","value":"The 2024 getting-started guide describes four months of domain history","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/","note":"Dated ad-partner guidance; confirm whether the current application still applies this condition."},{"label":"Review","value":"Original, human, brand-safe audience; Google account in good standing.","sourceUrl":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine/"},{"label":"Visitor location","value":"Journey Tier 1 countries","sourceUrl":"https://www.mediavine.com/application-requirements/","note":"The application names US, Canada, UK and Australia as examples."},{"label":"Content","value":"Original, engaging, human-written, brand-safe, regularly updated content","sourceUrl":"https://www.journeymv.com/affiliates-faq/"},{"label":"Google standing","value":"No active AdSense or Ad Exchange policy issues","sourceUrl":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine/"}],"revenueShare":"70%","paymentTerms":"Net 65","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Current requirements","url":"https://www.mediavine.com/application-requirements/"},{"label":"Approval criteria","url":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine/"},{"label":"Journey setup and terms","url":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"},{"label":"Journey FAQ","url":"https://www.journeymv.com/affiliates-faq/"},{"label":"Journey publisher page and supported regions","url":"https://get.journeymv.com/start-your-journey-1/"}],"joinSummary":"1,000 Tier 1 sessions in 30 days","minTraffic":null}, - "media-net": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 30","note":"Current publisher agreement says monthly payment for collected click-through revenue.","sourceUrl":"https://www.media.net/legal/"}],"joining":[{"label":"Traffic source","value":"Primarily organic traffic; no public numeric floor.","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Content","value":"Original, high-quality, navigable; restricted content excluded.","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Ads.txt","value":"Authorized ads.txt/app-ads.txt required.","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Traffic","value":"Mostly organic traffic","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Must connect","value":"Publish an authorized ads.txt or app-ads.txt file","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Prior provider history","value":"No prior suspension or termination by another monetization provider","sourceUrl":"https://www.media.net/legal/","note":"The agreement also requires disclosure of specified prior traffic or policy suspensions"}],"revenueShare":null,"paymentTerms":"Net 30 monthly on collected click-through revenue","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher agreement","url":"https://www.media.net/legal/"},{"label":"Inventory policy","url":"https://www.media.net/legal/inventorypolicies/"},{"label":"Publisher page","url":"https://www.media.net/publishers"}],"minTraffic":null}, - "mediavine": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Official revenue gate","value":"$5,000","note":"Site ad revenue in the previous calendar year; not last month or trailing 12 months.","sourceUrl":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Official share","value":"75%","note":"For new Official sites in 2026; Select/Signature/Premiere tiers are higher and legacy terms differ.","sourceUrl":"https://help.mediavine.com/revenue-share"},{"label":"Pay day","value":"Net 65","note":"Processed from month end.","sourceUrl":"https://help.mediavine.com/when-am-i-getting-paid-0"}],"joining":[{"label":"Revenue","value":"At least $5k site ad revenue in previous calendar year.","sourceUrl":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Content","value":"Original, audience-first, human and brand-safe; traffic sources and demographics reviewed.","sourceUrl":"https://help.mediavine.com/mediavine-official"},{"label":"Google standing","value":"Good standing with AdSense and Ad Exchange; no active policy issues.","sourceUrl":"https://help.mediavine.com/mediavine-official"},{"label":"Traffic","value":"No separate public PV/session gate for Official; revenue rule controls.","sourceUrl":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Application","value":"Provide analytics and allow site review; exact verification may vary.","sourceUrl":"https://prod.mediavine.com/blog/mediavine-application-process-faq/"},{"label":"Visitor location","value":"No fixed mix; traffic origin is reviewed","note":"Mediavine evaluates traffic location, sources, and demographics","sourceUrl":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine"},{"label":"Must connect","value":"Provide read-only Google Analytics access during review","sourceUrl":"https://prod.mediavine.com/blog/mediavine-application-process-faq/"}],"revenueShare":"75% for new Official sites in 2026; 80% Select, 85% Signature, 90% Premiere/Premiere Plus; legacy differs","paymentTerms":"Net 65 from month end; processed first business day","paymentThreshold":"$100 most methods; $200 eCheck/local transfer","paymentMethods":["PayPal","Domestic ACH","Wise","US wire","eCheck/local bank transfer","International wire"],"sources":[{"label":"Mediavine application requirements","url":"https://www.mediavine.com/application-requirements/"},{"label":"Approval criteria, including Journey","url":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine"},{"label":"2026 programs and revenue eligibility","url":"https://www.mediavine.com/programs/"},{"label":"Revenue share","url":"https://help.mediavine.com/revenue-share"},{"label":"Payment timing","url":"https://help.mediavine.com/when-am-i-getting-paid-0"},{"label":"Payment methods","url":"https://help.mediavine.com/mediavine-payment-methods"},{"label":"Application and Google Analytics access","url":"https://prod.mediavine.com/blog/mediavine-application-process-faq/"},{"label":"Google MCM onboarding","url":"https://help.mediavine.com/google-mcm-what-you-need-to-know"},{"label":"Publisher Path and previous-year revenue","url":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Official criteria","url":"https://help.mediavine.com/mediavine-official"}],"joinSummary":"$5,000 in site ad revenue in the prior calendar year","minTraffic":null}, - "mgid": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Visitor routes","value":"3,000 / 90,000","note":"Daily unique visitors OR monthly visitors, respectively.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Publisher share","value":"65% in Brazil offer","note":"Offer-specific; current Insertion Order controls global terms.","sourceUrl":"https://pr.mgid.com/monetize"},{"label":"Payment minimum","value":"$50–$1,000","note":"Method-specific in Brazil offer; not a universal threshold.","sourceUrl":"https://pr.mgid.com/monetize"}],"joining":[{"label":"Traffic","value":"3,000 daily unique visitors OR 90,000 monthly visitors.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Content","value":"10 current articles; original, policy compliant, no adult/torrent/redirect/low-info content.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Geography","value":"No traffic-source/country limitation in help; social visitors accepted; 70+ languages/200+ countries.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Review","value":"Usually email response within 24 hours; weekend to next business day.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Must connect","value":"Install MGID ad-unit code using the Dashboard instructions","sourceUrl":"https://www.mgid.com/services/publishers-tos"}],"revenueShare":"65% in Brazil offer; current insertion order controls","paymentTerms":"Net30 post-payment in Brazil offer; current insertion order controls","paymentThreshold":"Offer-specific: $50 Paymaster24; $100 PayPal/Tipalti; $1,000 ACH/international transfer","paymentMethods":["Paymaster24","PayPal","Tipalti","ACH","International transfer"],"sources":[{"label":"Publisher information","url":"https://help.mgid.com/publishers-general-information"},{"label":"Publisher terms updated July 2026","url":"https://www.mgid.com/services/publishers-tos"},{"label":"Brazil publisher offer","url":"https://pr.mgid.com/monetize"},{"label":"Publisher program","url":"https://www.mgid.com/publishers"},{"label":"Older publisher requirements, last updated 2020","url":"https://www.mgid.com/services/publisher-requirements"}],"minTraffic":null,"joinSummary":"3,000 unique visitors a day OR 90,000 visitors a month"}, - "monetizemore-pubguru": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Entry alternatives","value":"$5,000 / 2M","note":"Monthly ad revenue OR monthly pageviews, respectively.","sourceUrl":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"Revenue share","value":"Custom performance-based share","note":"Rate not public.","sourceUrl":"https://www.monetizemore.com/frequently-asked-questions/"}],"joining":[{"label":"Traffic","value":"PubGuru Premium: 2m monthly PV OR $5k monthly ad revenue.","sourceUrl":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"Product fit","value":"Managed yield optimization for larger, growing publishers.","sourceUrl":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"Terms","value":"Share and contract scope are set per publisher; confirm lock-in/notice in signed agreement.","sourceUrl":"https://www.monetizemore.com/frequently-asked-questions/"},{"label":"Must connect","value":"Flexible setup through the publisher’s Google Ad Manager and demand partners","note":"Product setup detail, not a separate eligibility rule.","sourceUrl":"https://www.monetizemore.com/blog/header-bidding-platforms/"},{"label":"Ad revenue","value":"$5,000 a month","note":"Alternative to 2,000,000 monthly pageviews.","sourceUrl":"https://www.monetizemore.com/solutions/pubguru-premium/"}],"joinSummary":"$5,000 monthly ad revenue OR 2 million monthly pageviews","revenueShare":"Custom performance share; no fixed percentage public","paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"PubGuru Premium requirements","url":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"PubGuru and header-bidding setup","url":"https://www.monetizemore.com/blog/header-bidding-platforms/"},{"label":"MonetizeMore revenue model","url":"https://www.monetizemore.com/blog/ezoic-vs-adsense-vs-monetizemore/"},{"label":"MonetizeMore FAQ","url":"https://www.monetizemore.com/frequently-asked-questions/"},{"label":"Publisher case","url":"https://www.monetizemore.com/case-study/if-publab-can-5x-their-ad-revenue-using-pubguru-so-can-you/"}],"minTraffic":{"amount":2000000,"unit":"pageviews"}}, - "monumetric": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Propel traffic","value":"10,000–80,000 pageviews/month","note":"At least 3 consecutive months; other programs have higher ranges.","sourceUrl":"https://www.monumetric.com/monumetric-program-overview/"},{"label":"Payment minimum","value":"$10","note":"Balance carries forward if below minimum.","sourceUrl":"https://www.monumetric.com/payment-terms-and-how-you-are-paid/"},{"label":"Pay day","value":"Net 60","note":"Paid monthly within first 10 business days.","sourceUrl":"https://www.monumetric.com/payment-terms-and-how-you-are-paid/"}],"joining":[{"label":"Traffic","value":"10k–80k PV/month for Propel for 3 consecutive months.","note":"No single-month shortcut.","sourceUrl":"https://www.monumetric.com/monumetric-program-overview/"},{"label":"Top markets","value":"At least 50% US/UK/Australia/Canada.","sourceUrl":"https://www.monumetric.com/what-are-the-requirements-to-work-with-monumetric/"},{"label":"Content","value":"English; no pornography, drugs, hate or copyright violations.","sourceUrl":"https://www.monumetric.com/what-are-the-requirements-to-work-with-monumetric/"},{"label":"Propel setup","value":"WordPress or Blogger, 300px sidebar, six ad slots, major-ad-provider approval, $99 implementation fee.","sourceUrl":"https://www.monumetric.com/requirements-for-the-propel-program/"}],"revenueShare":null,"paymentTerms":"Net 60; monthly within first 10 business days","paymentThreshold":"$10","paymentMethods":["US direct deposit","International PayPal"],"sources":[{"label":"Eligibility","url":"https://www.monumetric.com/what-are-the-requirements-to-work-with-monumetric/"},{"label":"Propel requirements","url":"https://www.monumetric.com/requirements-for-the-propel-program/"},{"label":"Google MCM setup","url":"https://www.monumetric.com/what-googles-switch-to-mcm-means-for-publishers/"},{"label":"Payment terms","url":"https://www.monumetric.com/payment-terms-and-how-you-are-paid/"},{"label":"Program overview","url":"https://www.monumetric.com/monumetric-program-overview/"}],"joinSummary":"10,000 pageviews/month for 3 consecutive months (Propel)","minTraffic":{"amount":10000,"unit":"pageviews"}}, - "newor-media": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Catapult","value":"60,000","note":"US pageviews a month; Elevate has no pageview minimum.","sourceUrl":"https://newormedia.com/programs"},{"label":"Premier","value":"420,000","note":"US pageviews a month.","sourceUrl":"https://newormedia.com/programs"},{"label":"Pay timing","value":"Net 30 / Net 45","note":"Public FAQ says Net30; platform agreement says Net45—signed contract controls.","sourceUrl":"https://newormedia.com/faqs/what-are-your-payment-terms"}],"joining":[{"label":"Elevate traffic","value":"No pageview minimum; manual review.","note":"Current Elevate FAQ.","sourceUrl":"https://newormedia.com/elevatefaqs"},{"label":"Catapult/Premier","value":"60k and 420k US PV/month respectively.","sourceUrl":"https://newormedia.com/programs"},{"label":"Elevate criteria","value":"English site, navigation, Google policy alignment, at least two ad units/page, GAM account and exclusivity.","sourceUrl":"https://newormedia.com/elevatefaqs"},{"label":"Language conflict","value":"Aug 2025 blog says non-English sites may be accepted with 50% US/UK/Canada/Australia traffic; current Elevate FAQ says sites must be English.","note":"Program-specific FAQ is current; confirm in writing.","sourceUrl":"https://newormedia.com/blog/ad-monetization-onboarding-with-newor-media/"},{"label":"Visitor location","value":"Traffic origin is reviewed; no fixed country share stated","sourceUrl":"https://newormedia.com/elevatefaqs"},{"label":"Approval time","value":"Usually responds within 24 to 48 hours","note":"This is the stated response window, not a guaranteed approval time","sourceUrl":"https://newormedia.com/elevatefaqs"}],"revenueShare":null,"paymentTerms":"Net30 in FAQ; Net45 in platform agreement","paymentThreshold":null,"paymentMethods":["PayPal","Payoneer","Direct wire transfer"],"sources":[{"label":"Programs","url":"https://newormedia.com/programs"},{"label":"Elevate FAQ","url":"https://newormedia.com/elevatefaqs"},{"label":"Platform agreement","url":"https://newormedia.com/node/296"},{"label":"Payment terms FAQ","url":"https://newormedia.com/faqs/what-are-your-payment-terms"},{"label":"Payment methods FAQ","url":"https://newormedia.com/faqs/how-do-you-make-payments"},{"label":"2025 onboarding blog","url":"https://newormedia.com/blog/ad-monetization-onboarding-with-newor-media/"}],"joinSummary":"Elevate: no pageview minimum, but manual review, English, GAM and exclusivity","minTraffic":{"amount":0,"unit":"pageviews","label":"Elevate has no pageview minimum; individual review"}}, - "nitropay": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 7","sourceUrl":"https://nitropay.com/docs/"},{"label":"Payout minimum","value":"None","note":"above transaction fees","sourceUrl":"https://nitropay.com/docs/"},{"label":"Go-live time","value":"5–7 business days","note":"after your site is accepted","sourceUrl":"https://nitropay.com/docs/"}],"joining":[{"label":"Traffic","value":"Traffic amount, pageviews and sessions are reviewed; no numeric minimum is published","sourceUrl":"https://nitropay.com/docs/"},{"label":"Visitor location","value":"Traffic geography is reviewed; accepted regions are not listed","sourceUrl":"https://nitropay.com/docs/"},{"label":"Content","value":"Site content and niche are reviewed; guidelines bar adult, illegal, violent, or infringing content","sourceUrl":"https://blog.nitropay.com/guidelines/"},{"label":"Must connect","value":"Install Nitro ad tags and provide a Google Ad Manager network code for Google MCM; GA4 access can speed review","sourceUrl":"https://nitropay.com/docs/"},{"label":"Approval time","value":"Initial review time not published; after quality acceptance it averages 5–7 business days to enough SSP coverage for go-live","sourceUrl":"https://nitropay.com/docs/","note":"Post-acceptance activation, not application approval."}],"revenueShare":null,"paymentTerms":"Net 7; paid seven days after the start of each month","paymentThreshold":"No fixed minimum; earnings must exceed the transaction fee","paymentMethods":["ACH (US)","EFT (Canada)","FPS (UK)","NPP (Australia)","BECS (New Zealand)","FPSHK (Hong Kong)","SEPA (Europe)","IACH","SWIFT wire"],"sources":[{"label":"Nitro Docs Archive: approval, onboarding, and payments","url":"https://nitropay.com/docs/"},{"label":"Publisher Guidelines","url":"https://blog.nitropay.com/guidelines/"},{"label":"Publisher application form","url":"https://nitropay.com/publishers/"}],"minTraffic":null}, - "opti-digital": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day, Managed Inventory","value":"Net 45","sourceUrl":"https://knowledge-base.optidigital.com/knowledge/understanding-your-payment-terms"},{"label":"Pay day, Managed Account","value":"Net 60","note":"for Opti demand; Google pays its own revenue on a 25-day cycle","sourceUrl":"https://knowledge-base.optidigital.com/knowledge/understanding-your-payment-terms"}],"joining":[{"label":"Traffic","value":"No universal numeric minimum published","sourceUrl":"https://optidigital.com/request-demo/"},{"label":"Must connect","value":"Demand Hub needs CMP, ads.txt, and bidder setup","note":"This is for the Demand Hub product; no general publisher sign-up requirement is stated.","sourceUrl":"https://knowledge-base.optidigital.com/knowledge/getting-started-with-demand-hub"},{"label":"Referral offer","value":"Referral-only conditions: tags live 3 months on most traffic, eligible geography, inventory quality, 3 ads/page, registered companies","sourceUrl":"https://optidigital.com/refer-a-publisher/"}],"revenueShare":"Share of gross programmatic revenue; percentage not public","paymentTerms":"Net 45 for Managed Inventory; Net 60 for Opti demand under Managed Account; Google pays direct on its 25-day cycle","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Payment terms by setup","url":"https://knowledge-base.optidigital.com/knowledge/understanding-your-payment-terms"},{"label":"Revenue-share and invoice flow","url":"https://knowledge-base.optidigital.com/knowledge/how-is-the-invoicing-working-at-opti-digital-when-will-i-get-paid"},{"label":"Demand Hub publisher integration","url":"https://knowledge-base.optidigital.com/knowledge/getting-started-with-demand-hub"},{"label":"Opti Digital publisher referral page (referral terms only)","url":"https://optidigital.com/refer-a-publisher/"},{"label":"Publisher monetization review","url":"https://optidigital.com/request-demo/"}],"minTraffic":null}, - "playwire": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Traffic to join","value":"500,000","note":"monthly website pageviews; apps need 1,000 daily active users","sourceUrl":"https://www.playwire.com/faq"},{"label":"Your share","value":"50% / 75%","note":"of net direct-sales / indirect-sales revenue","sourceUrl":"https://www.playwire.com/terms-of-service"},{"label":"Pay day","value":"Net 60","sourceUrl":"https://www.playwire.com/terms-of-service"}],"joining":[{"label":"Traffic","value":"FAQ: 500,000 monthly pageviews or 1,000 app DAU; qualification page suggests applying around 100,000 PV for full ROI","note":"100K guidance is not a confirmed admission cutoff.","sourceUrl":"https://www.playwire.com/faq"},{"label":"Visitor location","value":"20% US traffic is linked to higher revenue, not a hard eligibility rule","note":"At least 20% US traffic is associated with higher average revenue, not stated as an eligibility minimum","sourceUrl":"https://www.playwire.com/qualification-criteria"},{"label":"Content","value":"Unique, original content; IVT rating no higher than 7%","sourceUrl":"https://www.playwire.com/faq"},{"label":"Google standing","value":"Not blacklisted by Google or other major ad providers","sourceUrl":"https://www.playwire.com/faq"},{"label":"Must connect","value":"Implement the Playwire SDK","note":"Required to access Playwire demand","sourceUrl":"https://www.playwire.com/faq"},{"label":"Application","value":"Apply Now branches by website/blog, mobile app, and desktop application.","sourceUrl":"https://www.playwire.com/apply"}],"revenueShare":"50% of net direct-sales revenue; 75% of net indirect-sales revenue","paymentTerms":"Net 60 after month-end; paid in US dollars","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher FAQ","url":"https://www.playwire.com/faq"},{"label":"Qualification criteria","url":"https://www.playwire.com/qualification-criteria"},{"label":"Publisher terms of service","url":"https://www.playwire.com/terms-of-service"},{"label":"Apply Now page","url":"https://www.playwire.com/apply"}],"minTraffic":{"amount":500000,"unit":"pageviews"},"joinSummary":"Websites: 500,000 monthly pageviews; apps: 1,000 daily active users"}, - "pubfuture": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Traffic to join","value":"100,000","note":"monthly pageviews","sourceUrl":"https://pubfuture.com/home"},{"label":"Pay day","value":"Net 30","note":"January earnings are released at the end of February.","sourceUrl":"https://pubfuture.com/home"}],"joining":[{"label":"Traffic","value":"At least 100,000 pageviews a month","sourceUrl":"https://pubfuture.com/home"},{"label":"Content","value":"Safe content with no nudity","sourceUrl":"https://pubfuture.com/home"},{"label":"Must connect","value":"Add PubFuture lines to ads.txt; tags work inside Google Ad Manager and other ad servers","sourceUrl":"https://pubfuture.com/home"},{"label":"Application","value":"Create a publisher account and submit the website for review","sourceUrl":"https://ssp.pubfuture.com/auth/signup"}],"revenueShare":null,"paymentTerms":"Net 30; January earnings released at the end of February","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher FAQ (JavaScript-rendered)","url":"https://pubfuture.com/home"},{"label":"H5 game vendor case study","url":"https://blog.pubfuture.com/the-secret-to-400-more-revenue-for-h5-game-sites-pubfutures-winning-formula/1007/"},{"label":"Publisher contact form","url":"https://pubfuture.com/contact"},{"label":"Publisher onboarding and ads.txt article","url":"https://blog.pubfuture.com/ads-txt-and-ad-revenue-what-every-publisher-should-know/1236/"},{"label":"Publisher sign-up form","url":"https://ssp.pubfuture.com/auth/signup"}],"minTraffic":{"amount":100000,"unit":"pageviews"}}, - "pubgalaxy": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"No standalone PubGalaxy application; publisher supply migrated to Improve Digital","sourceUrl":"https://www.azerion.com/azerion-unifies-its-publisher-supply-business-under-improve-digital-launching-a-full-circle-solution-for-publishers/"},{"label":"Application","value":"No current PubGalaxy route; Improve Digital has separate Yield and SSP request-access routes","sourceUrl":"https://improvedigital.com/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Azerion announcement unifying publisher supply under Improve Digital","url":"https://www.azerion.com/azerion-unifies-its-publisher-supply-business-under-improve-digital-launching-a-full-circle-solution-for-publishers/"},{"label":"Current Improve Digital publisher offering","url":"https://improvedigital.com/"},{"label":"PubGalaxy official LinkedIn transition update","url":"https://gl.linkedin.com/company/pubgalaxy"}],"minTraffic":null,"joinSummary":"No standalone offer; migrated to Improve Digital"}, - "publift": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Revenue to join","value":"More than $2,000","note":"monthly ad revenue, in USD","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Revenue split","value":"80/20","note":"in Publift's 2026 comparison; confirm in your order","sourceUrl":"https://www.publift.com/blog/best-mediavine-alternatives"},{"label":"Payment minimum","value":"$200","sourceUrl":"https://www.publift.com/blog/the-best-cpm-ad-network-for-publishers"}],"joining":[{"label":"Traffic","value":"500,000 monthly pageviews is secondary; more than $2,000 monthly ad revenue is the main qualifying metric","note":"Monthly; revenue is the main qualifying metric.","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Visitor location","value":"Best fit: majority of traffic from the Americas, Europe, or Australia/New Zealand","note":"Publift says it works best with this mix; it does not state a strict geographic cutoff.","sourceUrl":"https://www.publift.com/how-it-works"},{"label":"Content","value":"Useful, original, appropriate, and brand-safe content","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Approval time","value":"Ad tags provided within five days","note":"The guide does not separate review time from the ad-tag delivery window.","sourceUrl":"https://www.publift.com/blog/the-best-cpm-ad-network-for-publishers"},{"label":"Ad revenue","value":"More than $2,000 a month","note":"Primary qualifying metric, in USD.","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Application","value":"The enquiry form asks for monthly revenue, monthly pageviews, and majority traffic location; these are intake fields, not approval guarantees.","sourceUrl":"https://www.publift.com/lets-talk"}],"revenueShare":"80/20 model according to Publift’s 2026 comparison; confirm in the order","paymentTerms":"Google revenue: Net 30; other ad-network revenue: Net 90","paymentThreshold":"$200 USD","paymentMethods":["Direct bank transfer"],"sources":[{"label":"Publift requirements","url":"https://www.publift.com/blog/publift-requirements"},{"label":"How it works and publisher fit","url":"https://www.publift.com/how-it-works"},{"label":"2026 Mediavine alternatives comparison and revenue split","url":"https://www.publift.com/blog/best-mediavine-alternatives"},{"label":"2026 CPM network payment and tag timing comparison","url":"https://www.publift.com/blog/the-best-cpm-ad-network-for-publishers"},{"label":"2026 payment comparison","url":"https://www.publift.com/blog/ezoic-vs-adsense-vs-publift"},{"label":"Publisher enquiry form","url":"https://www.publift.com/lets-talk"}],"minTraffic":null,"joinSummary":"More than $2,000 monthly ad revenue; pageviews are secondary"}, - "publir": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Programmatic share","value":"85%","note":"publisher share; Publir retains 15%, before applicable service costs","sourceUrl":"https://publir.com/service_agreement"},{"label":"Custom ad share","value":"50/50","note":"publisher and Publir; Net 60","sourceUrl":"https://publir.com/service_agreement"},{"label":"Pro fee","value":"15%","note":"separate from custom ad sales","sourceUrl":"https://publir.com/app/pricing"}],"joining":[{"label":"Traffic","value":"Pricing lists no traffic limit, but general admission criteria are not fully disclosed","sourceUrl":"https://publir.com/app/pricing","note":"Not a guarantee of acceptance at any traffic level."},{"label":"Must connect","value":"Install Publir's software and ad tags","sourceUrl":"https://publir.com/service_agreement"},{"label":"Exclusivity","value":"Non-exclusive service agreement","sourceUrl":"https://publir.com/service_agreement"},{"label":"Notice","value":"Either party may terminate with 30 days’ notice","sourceUrl":"https://publir.com/service_agreement"}],"revenueShare":"Programmatic and subscriptions: 85% to publisher before service costs; custom ads: 50/50; crowdfunding: Publir retains 15%","paymentTerms":"Programmatic and custom ads: at least monthly on Net 60; subscriptions: paid at least monthly","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Pricing","url":"https://publir.com/app/pricing"},{"label":"Service agreement","url":"https://publir.com/service_agreement"}],"minTraffic":null}, - "publisher-collective": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Revenue signal","value":"About €300","note":"EUR daily review signal; not a hard joining cutoff","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Payment minimum","value":"€500","sourceUrl":"https://www.publisher-collective.com/partners-terms-conditions"}],"joining":[{"label":"Traffic","value":"Core niches are reviewed individually; other niches need 3,000,000 monthly pageviews","note":"Tech, gaming, education, sports and health are core. Hobby coverage needs confirmation. EUR 300 daily revenue is a flexible review signal.","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Visitor location","value":"Works with publishers globally","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Content","value":"Unique, high-quality content and reputable traffic","note":"No bots or fraudulent traffic; partner terms also prohibit specified content","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Must connect","value":"Add Publisher Collective JavaScript tags to the site","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Approval time","value":"Application decision time not public; setup takes 10–14 days, full ramp target up to 30 days","note":"The FAQ gives 10 to 14 days for onboarding setup, not application approval","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Niches","value":"Technology, video gaming, hobbies and casual games, knowledge and education, sports, health and wellbeing","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Ad revenue","value":"About €300/day is a flexible evaluation signal; lower sites may be considered for growth or quality","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Application","value":"Site owners can submit a publisher partner enquiry; review is sales-led.","sourceUrl":"https://www.publisher-collective.com/publisher-contact"}],"revenueShare":"Set individually in the Advertising Representation Agreement","paymentTerms":"Paid after the advertiser pays; the partner terms set no fixed interval","paymentThreshold":"€500","paymentMethods":["Bank transfer; GBP, EUR, or USD"],"sources":[{"label":"Publisher FAQ and current eligibility","url":"https://www.publisher-collective.com/faq"},{"label":"Partner terms","url":"https://www.publisher-collective.com/partners-terms-conditions"},{"label":"Publisher contact form","url":"https://www.publisher-collective.com/publisher-contact"}],"joinSummary":"Core genres are reviewed individually; other niches need 3 million pageviews a month","minTraffic":null}, - "pubnation": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"No current standalone PubNation offer; migration to Mediavine is complete","sourceUrl":"https://help.mediavine.com/"},{"label":"Application","value":"Discontinued; use a current Mediavine route if eligible","sourceUrl":"https://help.mediavine.com/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Official publisher homepage","url":"https://www.pubnation.com/"},{"label":"Mediavine migration status","url":"https://help.mediavine.com/"}],"minTraffic":null,"joinSummary":"No standalone offer; migrated to Mediavine"}, - "pubpower": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Dated traffic figure","value":"100,000","note":"approximate and dated; not a confirmed joining cutoff","sourceUrl":"https://help.pubpower.io/article/website-requirements"},{"label":"Pay day","value":"Net 0 or Net 30","note":"Net 0 above $3,000 a month in revenue","sourceUrl":"https://help.pubpower.io/article/payment-methods-terms"},{"label":"Payoneer minimum","value":"$50","sourceUrl":"https://help.pubpower.io/article/payment-methods-terms"}],"joining":[{"label":"Traffic","value":"Around 100,000 monthly visitors in an old help page; current threshold needs reconfirmation","note":"Page last updated about three years ago; not a confirmed current strict floor.","sourceUrl":"https://help.pubpower.io/article/website-requirements"},{"label":"Content","value":"Follow PubPower’s content policy; no pop-ups or redirects","note":"Policy bars or restricts sexual, explicit, violent, illegal, gambling, and infringing content.","sourceUrl":"https://help.pubpower.io/article/website-requirements"},{"label":"Must connect","value":"Add PubPower ads.txt lines and install its ad tag","sourceUrl":"https://help.pubpower.io/article/quick-start-to-your-monetization"},{"label":"Approval time","value":"Usually 24–48 hours to 2–4 days","note":"Quick-start says 24–48 hours; FAQ says 2–4 days and warns weekends can take longer.","sourceUrl":"https://help.pubpower.io/article/quick-start-to-your-monetization"},{"label":"Application","value":"Create a PubPower publisher account, add the domain, then wait for the publisher QA review.","sourceUrl":"https://help.pubpower.io/article/quick-start-to-your-monetization"}],"revenueShare":"Essential publisher revenue share is tailored; Advanced custom bidders have no revenue share","paymentTerms":"Net 0 above $3,000 monthly revenue; otherwise Net 30","paymentThreshold":"Bank: $1,000; PayPal: $100; Payoneer: $50; crypto: BCH $100, BTC $1,000, USDT-ERC20 $1,000","paymentMethods":["Bank transfer","PayPal","Payoneer","Cryptocurrency (BCH, BTC, USDT-ERC20)"],"sources":[{"label":"PubPower publisher packages and revenue-sharing model","url":"https://pubpower.io/"},{"label":"Website requirements","url":"https://help.pubpower.io/article/website-requirements"},{"label":"Content policies","url":"https://help.pubpower.io/article/content-policies"},{"label":"Quick-start and domain review","url":"https://help.pubpower.io/article/quick-start-to-your-monetization"},{"label":"Publisher FAQ and review timing","url":"https://help.pubpower.io/article/getting-started-faqs"},{"label":"Payment methods and terms","url":"https://help.pubpower.io/article/payment-methods-terms"},{"label":"Publisher account registration","url":"https://apps.pubpower.io/register"}],"minTraffic":null}, - "raptive": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Traffic to join","value":"25,000","note":"monthly pageviews","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Your share","value":"75%","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360035078152-Getting-Started-with-Raptive"},{"label":"Pay day","value":"Net 45","sourceUrl":"https://help.raptive.com/hc/en-us/articles/202052255-When-do-I-get-paid"}],"joining":[{"label":"Traffic","value":"At least 25,000 monthly pageviews; 50% named-country share below 100K PV and 40% at/above 100K","note":"Monthly","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Visitor location","value":"50% from five countries at 25k–99,999 pageviews; 40% at 100k+","note":"US, UK, Canada, New Zealand, or Australia","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Content","value":"High-quality, original, human-led content","note":"Long-form content on most pages at 25k–99,999 pageviews","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Must connect","value":"Google Analytics access","note":"Read-only authorization","sourceUrl":"https://help.raptive.com/hc/en-us/articles/6681661647515-Applying-to-Raptive-with-Google-Analytics"},{"label":"Domain age","value":"At least six months","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Application","value":"Apply in the Raptive dashboard after confirming traffic, country share, domain age, and Google Analytics criteria.","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"}],"revenueShare":"75%","paymentTerms":"Net 45; paid on the 15th","paymentThreshold":"$25 for most methods; $100 for wire transfer","paymentMethods":["US direct deposit","eCheck or local bank transfer","PayPal","Paper check","Wire transfer"],"sources":[{"label":"Creator eligibility","url":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Applying with Google Analytics","url":"https://help.raptive.com/hc/en-us/articles/6681661647515-Applying-to-Raptive-with-Google-Analytics"},{"label":"Getting started and share","url":"https://help.raptive.com/hc/en-us/articles/360035078152-Getting-Started-with-Raptive"},{"label":"Payment timing","url":"https://help.raptive.com/hc/en-us/articles/202052255-When-do-I-get-paid"},{"label":"Payment methods","url":"https://help.raptive.com/hc/en-us/articles/360013465192-Payment-Methods"},{"label":"Payment thresholds","url":"https://help.raptive.com/hc/en-us/articles/360025379831-Why-didn-t-I-get-my-Raptive-payment"},{"label":"Raptive publisher dashboard application route (direct preview is session-gated)","url":"https://dashboard.raptive.com/"}],"minTraffic":{"amount":25000,"unit":"pageviews"}}, - "refinery89": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Payment minimum","value":"€250 or $250","note":"a month, by region","sourceUrl":"https://refinery89.com/docs/faqs/finance/is-there-a-minimum-threshold-for-getting-paid/"}],"joining":[{"label":"Traffic","value":"No numeric traffic minimum published; contact team for site review","sourceUrl":"https://refinery89.com/docs/faqs/requirements/am-i-eligible/"},{"label":"Visitor location","value":"Worldwide service availability; no qualifying country share published","sourceUrl":"https://refinery89.com/docs/faqs/about-refinery89/where-does-refinery89-operate-and-offer-its-services/"},{"label":"Must connect","value":"Single Tag for websites or SDK for apps","sourceUrl":"https://refinery89.com/docs/faqs/onboarding/how-can-i-start-working-with-refinery89/"},{"label":"Application","value":"Get Started form accepts a site or app and asks for a pageview band and country; no admission cutoff is published.","sourceUrl":"https://refinery89.com/get-started/"}],"revenueShare":null,"paymentTerms":"Ask your Publisher Success Manager; no standard schedule is published","paymentThreshold":"€250 or $250, based on region","paymentMethods":["Bank transfer","eCheck","Wire transfer","PayPal"],"sources":[{"label":"Eligibility","url":"https://refinery89.com/docs/faqs/requirements/am-i-eligible/"},{"label":"Publisher service locations","url":"https://refinery89.com/docs/faqs/about-refinery89/where-does-refinery89-operate-and-offer-its-services/"},{"label":"How to start and connect","url":"https://refinery89.com/docs/faqs/onboarding/how-can-i-start-working-with-refinery89/"},{"label":"Payment threshold","url":"https://refinery89.com/docs/faqs/finance/is-there-a-minimum-threshold-for-getting-paid/"},{"label":"Payment timing","url":"https://refinery89.com/docs/faqs/finance/when-do-publishers-get-paid/"},{"label":"Payment methods","url":"https://refinery89.com/docs/faqs/finance/what-are-the-payment-methods-available/"},{"label":"Publisher and app Get Started form","url":"https://refinery89.com/get-started/"}],"minTraffic":null}, - "revcontent": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Agreement payment floor","value":"$250","note":"payment-method minimums may differ","sourceUrl":"https://help.revcontent.com/knowledge/publisher-terms"}],"joining":[{"label":"Traffic","value":"No minimum pageviews or traffic; publisher account and every domain are reviewed case by case","sourceUrl":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account","note":"Applications are reviewed case by case"},{"label":"Content","value":"No illegal, infringing, pornographic, obscene, illicit-drug, or unlawful-gambling content","sourceUrl":"https://help.revcontent.com/knowledge/publisher-terms"},{"label":"Must connect","value":"After domain approval, Revcontent sends placement choices and widget code","sourceUrl":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account"},{"label":"Approval time","value":"Each application and domain is reviewed by a human team; no timing is stated","sourceUrl":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account"}],"revenueShare":"Set in the applicable account or order terms; no standard public rate","paymentTerms":null,"paymentThreshold":"$250 in the agreement; method minimums are $100 for US ACH, $100 for international PayPal, and $1,000 for international wire","paymentMethods":["ACH (US)","PayPal (international)","Wire transfer (international)"],"sources":[{"label":"Publisher account creation and approval","url":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account"},{"label":"Publisher terms","url":"https://help.revcontent.com/knowledge/publisher-terms"},{"label":"Publisher payment requirements and methods","url":"https://help.revcontent.com/knowledge/publisher-payment-requirements-methods"}],"minTraffic":{"amount":0,"unit":"pageviews"}}, - "setupad": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Self-Serve entry","value":"10M+ monthly visits","note":"Free plan; separate from managed-service admission","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Business Self-Serve fee","value":"€0.05 per 1,000 ad requests","note":"€50 minimum prepayment","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Enterprise spend","value":"€1,000 minimum monthly spend","note":"50M+ visits; separate self-serve offer","sourceUrl":"https://setupad.com/setupad-self-serve/"}],"joining":[{"label":"Traffic","value":"Self-Serve plans start at 10M monthly visits; managed service has no verified current public cutoff","note":"Company articles conflict between 100,000 monthly visitors and sessions. Self-Serve is separate.","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Visitor location","value":"Main traffic from the US, UK, Canada, or Australia","sourceUrl":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Content","value":"Follow Setupad’s quality policy; low-quality sites are not accepted","sourceUrl":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Must connect","value":"Add ads.txt and use Setupad demand sources, including Google","sourceUrl":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Self-Serve","value":"Requires own GAM, direct SSPs, a high-quality site, and internal approval","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Application","value":"The live form is for Self-Serve and requests a plan plus Google Analytics domain verification; it does not define managed-service eligibility.","sourceUrl":"https://signup.setupad.com/?lang=en&type=7"}],"revenueShare":"Optional Setupad reseller demand takes 20%; Self-Serve direct demand uses a €0.05 CPM fee","paymentTerms":"Net 60 for published managed terms and optional reseller demand; direct SSP partners pay under their own agreements","paymentThreshold":"$100 in published managed guidance; reseller-demand threshold not stated","paymentMethods":["Bank transfer","PayPal","Payoneer","Wise","Revolut","Paysera"],"sources":[{"label":"Setupad publisher requirements and payment guidance","url":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Setupad Self-Serve plans, requirements and billing","url":"https://setupad.com/setupad-self-serve/"},{"label":"Setupad current platform overview","url":"https://setupad.com/"},{"label":"Setupad SSP guide","url":"https://setupad.com/blog/top-ssps-for-publishers/"},{"label":"Setupad Self-Serve application","url":"https://signup.setupad.com/?lang=en&type=7"}],"minTraffic":null,"joinSummary":"Self-Serve: 10M+ monthly visits; managed service has no confirmed current public floor"}, - "she-media": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Traffic to join","value":"50,000+","note":"monthly pageviews; smaller sites may be considered case by case","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Your share","value":"75%","note":"gross open-marketplace programmatic revenue; guide updated March 2023","sourceUrl":"https://www.shemedia.com/member-spotlight/member-guide-for-partners"},{"label":"Pay day","value":"Net 35","sourceUrl":"https://www.shemedia.com/partner-network/faqs"}],"joining":[{"label":"Traffic","value":"At least 50,000 pageviews/month for standard approval; smaller sites may be considered case by case","note":"Smaller sites can receive individual review; no lower numeric requirement is published","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Visitor location","value":"At least 30% US traffic","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Content","value":"Original, high-quality, brand-safe, human-led content","note":"AI-generated images need a clear disclaimer","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Google standing","value":"Good track record with Google ad products and platforms","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Must connect","value":"Google MCM and ads.txt","note":"MCM and ads.txt are part of the onboarding requirements","sourceUrl":"https://support.shemedia.com/hc/en-us/articles/19361668032027-Onboarding-Guide-for-Wordpress-Sites"},{"label":"Approval time","value":"3–4 weeks for a response","note":"Response timing, not a final decision guarantee","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Analytics","value":"Google Analytics access","sourceUrl":"https://apply.shemedia.com/"},{"label":"Site age","value":"At least 90 days","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Ad inventory","value":"Exclusive mobile and desktop banner ads","note":"Affiliate links and banners are exceptions","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Application","value":"Form asks average monthly GA pageviews, current ad networks, and publishing platform; it says Wix-built sites are ineligible and asks for GA access to verify traffic.","sourceUrl":"https://apply.shemedia.com/"}],"revenueShare":"75% of gross open-marketplace programmatic revenue; up to 75% of direct and private marketplace revenue (Member Guide updated 2023)","paymentTerms":"Net 35 after the month closes","paymentThreshold":"$20 (Member Guide updated 2023)","paymentMethods":["US direct deposit/ACH","US check if ACH is unavailable","PayPal for international publishers","Wise/ACH or wire for international publishers if approved"],"sources":[{"label":"Partner FAQ","url":"https://www.shemedia.com/partner-network/faqs"},{"label":"Publisher application","url":"https://apply.shemedia.com/"},{"label":"WordPress onboarding guide","url":"https://support.shemedia.com/hc/en-us/articles/19361668032027-Onboarding-Guide-for-Wordpress-Sites"},{"label":"Blogger onboarding guide","url":"https://support.shemedia.com/hc/en-us/articles/30950543170971-Onboarding-Guide-for-Blogger-Sites"},{"label":"Member Guide, updated March 2023","url":"https://www.shemedia.com/member-spotlight/member-guide-for-partners"},{"label":"International payment options, updated April 2025","url":"https://support.shemedia.com/hc/en-us/articles/35987097977627-Payment-Options-for-Publishers-Outside-the-US"}],"joinSummary":"50,000 pageviews a month; smaller sites need individual review","minTraffic":{"amount":50000,"unit":"pageviews"}}, - "snack-media": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Referral program traffic","value":"1 million","note":"impressions; not a general minimum, only for referral eligibility","sourceUrl":"https://www.snack-media.com/referral/"},{"label":"Notice to leave","value":"30 days","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"}],"joining":[{"label":"Traffic","value":"No universal traffic floor in 2025 agreement; 1M impressions applies only to referral eligibility","note":"This is referral-program eligibility; the publisher agreement does not state a universal traffic floor","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"},{"label":"Content","value":"No hateful, pornographic, illegal, or infringing content","note":"The referral program also excludes sensitive and gambling sites","sourceUrl":"https://www.snack-media.com/brand-safety-policy/"},{"label":"Google standing","value":"Must comply with Google policies for the referral program","sourceUrl":"https://www.snack-media.com/referral/"},{"label":"Must connect","value":"Share live traffic stats and install network tags","note":"Tags must be live within 14 days of the agreement date","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"},{"label":"Exclusivity","value":"Standard agreement gives Snack exclusive rights for its defined services and specified ad/content sales scope; confirm cover sheet","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"}],"revenueShare":null,"paymentTerms":"Within 30 days after the month when Snack Media receives the advertiser funds","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher referral eligibility","url":"https://www.snack-media.com/referral/"},{"label":"Brand safety policy","url":"https://www.snack-media.com/brand-safety-policy/"},{"label":"2025 publisher agreement terms","url":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"},{"label":"Terms page","url":"https://www.snack-media.com/Ad_Network_Terms_and_Conditions_for_Publishers/"},{"label":"Snack publisher services","url":"https://www.snack-media.com/publishers/"}],"minTraffic":null}, - "sovrn": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Visits to join","value":"More than 100,000","note":"visits a month, strictly more than 100,000","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Pay day","value":"Net 45","note":"for most direct publishers; terms may vary","sourceUrl":"https://authcore.atlassian.net/wiki/spaces/KB/pages/2693627510846"},{"label":"Payment minimum","value":"$25","note":"$50 for wire","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-the-minimum-payment-threshold-for-sovrn"}],"joining":[{"label":"Traffic","value":"Strictly more than 100,000 visits/month for Ad Exchange; visits are not sessions","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property","note":"Visits are not sessions."},{"label":"Content","value":"Current content published within 4 months, privacy and brand-safety policies, not child-directed or exclusively under 16","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Must connect","value":"Provide an operational business domain and current ads.txt ownership details; intermediaries also need sellers.json","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Approval time","value":"Depends on external checks and possible manual review; no fixed estimate. File updates often show within about two business days.","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-sovrn-s-approval-process-for-the-ad-exchange"},{"label":"Application","value":"Create a platform account first, then start Ad Exchange onboarding; Sovrn Commerce is a separate product.","sourceUrl":"https://platform.sovrn.com/account/signup"}],"revenueShare":null,"paymentTerms":"Net 45 for most Ad Exchange publishers; individual publisher terms may vary","paymentThreshold":"$25 standard; $50 for wire transfer","paymentMethods":["PayPal","ACH (US)","Wire transfer","eCheck/local transfer","Paper check (US)"],"sources":[{"label":"Property approval requirements","url":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Approval process and timing","url":"https://knowledge.sovrn.com/kb/what-is-sovrn-s-approval-process-for-the-ad-exchange"},{"label":"Current payment methods","url":"https://knowledge.sovrn.com/kb/how-do-i-get-paid-for-the-ad-exchange"},{"label":"Payment thresholds","url":"https://knowledge.sovrn.com/kb/what-is-the-minimum-payment-threshold-for-sovrn"},{"label":"Payment timing guide","url":"https://authcore.atlassian.net/wiki/spaces/KB/pages/2693627510846"},{"label":"Ad Exchange account sign-up","url":"https://platform.sovrn.com/account/signup"}],"minTraffic":{"amount":100000,"unit":"visits","label":"More than 100,000 visits a month"}}, - "sparteo": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"Sales-led; no public numeric entry minimum found","sourceUrl":"https://corporate.sparteo.com/"},{"label":"Application","value":"Contact the team for a sales-led publisher discussion; no self-serve application gate is published.","sourceUrl":"https://corporate.sparteo.com/contact"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Company and publisher services","url":"https://corporate.sparteo.com/"},{"label":"Publisher technology privacy notice","url":"https://corporate.sparteo.com/services-privacy-notice"},{"label":"Publisher platform case study","url":"https://corporate.sparteo.com/post/w3schools-rpm-uplift-unified-monetisation-stack"},{"label":"Company expansion update","url":"https://corporate.sparteo.com/post/sparteo-strengthens-supply-leadership-to-accelerate-global-expansion"},{"label":"Sparteo company LinkedIn","url":"https://www.linkedin.com/company/sparteo"},{"label":"Publisher contact form","url":"https://corporate.sparteo.com/contact"}],"minTraffic":null}, - "taboola": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Within 45 days","note":"after the month ends","sourceUrl":"https://www.taboola.com/policies/publisher-revshare-terms-and-conditions-1-3-US"}],"joining":[{"label":"Traffic","value":"No numeric minimum stated in current account guide","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Content","value":"Own or properly license content and follow the Publisher Policy","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Must connect","value":"Install Taboola widget code","sourceUrl":"https://developers.taboola.com/web-integrations/docs/welcome"},{"label":"Approval time","value":"Team responds within 48 hours","note":"This is response timing, not final approval","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Applicant age","value":"At least 18 years old","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Exclusivity","value":"Exclusive only against other third-party recommendation services under current guide","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"}],"revenueShare":"Set in the applicable publisher Order; no standard public percentage","paymentTerms":"Within 45 days after month-end; paid in US dollars","paymentThreshold":null,"paymentMethods":["Payoneer (most publishers); exceptions vary by location"],"sources":[{"label":"New publisher account guide","url":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Publisher policy","url":"https://pubhelp.taboola.com/hc/en-us/articles/360033751274-Taboola-Publisher-Policy"},{"label":"Web integration guide","url":"https://developers.taboola.com/web-integrations/docs/welcome"},{"label":"Publisher revenue-share terms","url":"https://www.taboola.com/policies/publisher-revshare-terms-and-conditions-1-3-US"},{"label":"Account setup and payment methods","url":"https://pubhelp.taboola.com/hc/en-us/articles/360003157054-Account-Set-Up"}],"minTraffic":null}, - "teads": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"Sales-led media-owner partnership; no public numeric threshold","sourceUrl":"https://www.teads.com/media-owners"}],"revenueShare":"Terms vary by contract; revenue-share rates may vary with pageview volume or total revenue","paymentTerms":"Negotiated in the media-partner contract; no standard payment interval is published","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Media owner partnership","url":"https://www.teads.com/media-owners"},{"label":"2025 annual filing describing publisher agreements and negotiated payment timing","url":"https://www.sec.gov/Archives/edgar/data/1454938/000145493826000015/tead-20251231.htm"}],"minTraffic":null}, - "the-moneytizer": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Organic audience to join","value":"300,000 unique visitors","note":"Organic traffic in prior 30 days; US/UK/CA","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Publisher share","value":"At least 70%","note":"pre-tax advertiser billings under terms","sourceUrl":"https://www.themoneytizer.com/cgu"},{"label":"Payout threshold","value":"€50 or $50","note":"market-specific","sourceUrl":"https://us.themoneytizer.com/category/payments-invoices"}],"joining":[{"label":"Traffic","value":"At least 300,000 unique visitors from organic traffic in the 30 days before registration; qualifying countries US/UK/CA","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Visitor location","value":"Qualifying organic unique visitors must be from the US, UK, or Canada","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Content","value":"Own domain/server access, sufficient current content; listed restricted categories excluded","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Must connect","value":"Install ads.txt and ad tags; use a consent banner for EU traffic","sourceUrl":"https://us.themoneytizer.com/publisherguide"},{"label":"Approval time","value":"A quality review follows website registration; no timing stated","sourceUrl":"https://us.themoneytizer.com/publisherguide"},{"label":"Application","value":"Register an account, add the owned website in the portal, then wait for a quality review.","sourceUrl":"https://us.themoneytizer.com/"}],"revenueShare":"At least 70% of the pre-tax amount billed to advertisers under published Terms","paymentTerms":"An invoice is generated automatically at the threshold and is payable 60 days after its issue date","paymentThreshold":"€50 under the published Terms; $50 in the US publisher FAQ","paymentMethods":["Bank transfer","PayPal"],"sources":[{"label":"Publisher terms and conditions","url":"https://www.themoneytizer.com/cgu"},{"label":"Publisher guide","url":"https://us.themoneytizer.com/publisherguide"},{"label":"US payments FAQ","url":"https://us.themoneytizer.com/category/payments-invoices"},{"label":"US validation criteria","url":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"US publisher sign-up page","url":"https://us.themoneytizer.com/"}],"minTraffic":null,"joinSummary":"300,000 unique organic visitors in prior 30 days from US/UK/CA"}, + "adapex": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 60","note":"Paid monthly. Adapex doesn't publish a payout minimum or revenue share.","sourceUrl":"https://adapex.io/faqs/"}],"joining":[{"label":"Application","value":"The sign-up form asks for monthly revenue, monthly pageviews, and share of US traffic","note":"Adapex doesn't say what numbers it accepts.","sourceUrl":"https://adapex.io/get-in-touch/"},{"label":"Traffic","value":"No minimum published. Adapex pitches itself to mid-sized publishers.","sourceUrl":"https://adapex.io/faqs/"},{"label":"Must connect","value":"Google Ad Manager through MCM","note":"Adapex says it works with existing AdSense, AdX and Prebid.","sourceUrl":"https://adapex.io/faqs/"},{"label":"Contract","value":"No long-term contract required to start; longer terms can be offered","sourceUrl":"https://adapex.io/faqs/"}],"revenueShare":null,"paymentTerms":"Net 60, paid monthly by the payment method you set up","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher FAQ","url":"https://adapex.io/faqs/"},{"label":"Contact","url":"https://adapex.io/get-in-touch/"}],"minTraffic":null}, + "aditude": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Application","value":"Book a demo or contact sales. There's no self-serve sign-up.","note":"Aditude covers websites and apps, with service set up for each publisher.","sourceUrl":"https://www.aditude.com/contact"},{"label":"Traffic","value":"No traffic minimum published.","sourceUrl":"https://www.aditude.com/case-studies"},{"label":"Publisher types","value":"Case studies cover media, gaming, audio and apps.","sourceUrl":"https://www.aditude.com/case-studies"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Aditude publisher guidance","url":"https://www.aditude.com/blog/the-5-questions-every-publisher-should-ask-their-monetization-partner"},{"label":"Publisher platform","url":"https://www.aditude.com/"},{"label":"Website terms","url":"https://www.aditude.com/policies/terms"},{"label":"Case studies","url":"https://www.aditude.com/case-studies"},{"label":"Dexerto case study","url":"https://www.aditude.com/case-studies/dexerto"},{"label":"Publisher contact","url":"https://www.aditude.com/contact"}],"minTraffic":null}, + "adnimation": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 35","note":"For the standard managed service. MAP has its own terms.","sourceUrl":"https://www.adnimation.com/faqs/"}],"joining":[{"label":"Standard traffic","value":"Large sites; no official number.","note":"Adnimation's CEO once called 1 to 20 million monthly visitors its sweet spot. That's not a cutoff.","sourceUrl":"https://www.adnimation.com/adnimation-co-founder-talks-plans-for-growth-with-mergermarket/"},{"label":"MAP traffic","value":"No pageview minimum; every site is reviewed.","sourceUrl":"https://www.adnimation.com/map/"},{"label":"Content","value":"No gambling, pornography, drugs or similar content.","sourceUrl":"https://www.adnimation.com/faqs/"},{"label":"Google standing","value":"Follow relevant Google policy guidelines","sourceUrl":"https://www.adnimation.com/faqs/"},{"label":"Must connect","value":"Share analytics access and connect Google Ad Manager, or have one created; install the ad code","sourceUrl":"https://www.adnimation.com/faqs/"}],"revenueShare":"Revenue share; the percentage isn't public","paymentTerms":"Net 35 after month end","paymentThreshold":null,"paymentMethods":["ACH (US)","International wire","PayPal by arrangement"],"sources":[{"label":"Publisher FAQ","url":"https://www.adnimation.com/faqs/"},{"label":"MAP program","url":"https://www.adnimation.com/map/"},{"label":"MAP terms","url":"https://www.adnimation.com/map-terms-conditions/"},{"label":"Co-founder interview","url":"https://www.adnimation.com/adnimation-co-founder-talks-plans-for-growth-with-mergermarket/"},{"label":"Case study","url":"https://www.adnimation.com/case-study/"}],"minTraffic":null}, + "adpushup": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Payment minimum","value":"$50","note":"From AdPushup's terms revised April 20, 2021, the latest it has published.","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"},{"label":"Pay day","value":"Net 45","note":"From the same 2021 terms.","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"}],"joining":[{"label":"Traffic","value":"No minimum published.","note":"AdPushup's own blog says there's no pageview minimum. Its sign-up page doesn't mention traffic.","sourceUrl":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"},{"label":"Revenue","value":"About $3,000 a month, per AdPushup's own blog","note":"That's a blog post, not a stated rule. Its sign-up page doesn't list a minimum.","sourceUrl":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"},{"label":"Application","value":"Submit your site for review, then install the approved code.","sourceUrl":"https://www.adpushup.com/publisher/get-started/"},{"label":"Content","value":"No illegal, pornographic, hateful, fraudulent, or infringing content; no invalid activity","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"},{"label":"Must connect","value":"Install AdPushup code on approved sites","note":"AdPushup can approve or reject a site.","sourceUrl":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"}],"revenueShare":"Set in a separate publisher agreement; not public","paymentTerms":"Net 45, from the 2021 terms","paymentThreshold":"$50, from the 2021 terms","paymentMethods":["Direct deposit","Wire transfer","PayPal"],"sources":[{"label":"AdPushup blog: ad networks for small publishers","url":"https://www.adpushup.com/blog/best-ad-networks-for-small-publishers/"},{"label":"Terms (revised 2021)","url":"https://www.adpushup.com/wp-content/uploads/2022/04/Terms-of-Service.pdf"},{"label":"Onboarding","url":"https://www.adpushup.com/publisher/get-started/"}],"minTraffic":null}, + "amazon-publisher-services": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"UAM fee","value":"10%","note":"From Amazon's 2023 FAQ. The current UAM page doesn't state a rate.","sourceUrl":"https://aps.amazon.com/aps/wp-content/uploads/2023/01/aps_uam_update_011123-FINAL.pdf"},{"label":"Payment minimum","value":"$5 USD","note":"From the current UAM page.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"},{"label":"Pay day","value":"Net 60","note":"From the current UAM page.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"}],"joining":[{"label":"Application","value":"Invitation only. You need an invite code and an approved site.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"},{"label":"Must connect","value":"Google Ad Manager. You must represent the site directly or exclusively.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"},{"label":"Geography","value":"Worldwide availability; bidder demand varies by country.","sourceUrl":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"}],"revenueShare":null,"paymentTerms":"Net 60","paymentThreshold":"$5 USD","paymentMethods":[],"sources":[{"label":"UAM FAQ / fee terms","url":"https://aps.amazon.com/aps/wp-content/uploads/2023/01/aps_uam_update_011123-FINAL.pdf"},{"label":"Current UAM page","url":"https://publishers.advertising.a2z.com/aps/unified-ad-marketplace/index.html"}],"minTraffic":null}, + "anymind-anymanager": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Product fit","value":"Ads for websites and apps, with 150+ ad partners, an app SDK, video, and private marketplace deals.","sourceUrl":"https://anymindgroup.com/products/anymanager/"},{"label":"Application","value":"Contact AnyMind. There's no self-serve sign-up.","sourceUrl":"https://anymindgroup.com/products/anymanager/"},{"label":"Regional fit","value":"Mainly Asia-Pacific, with wider coverage on offer.","sourceUrl":"https://anymindgroup.com/case-study/?business=publishergrowth&pa=2"},{"label":"Must connect","value":"AnyManager tag for web or SDK for apps","sourceUrl":"https://anymindgroup.com/news/press-release/anymanagergames-launch/"}],"revenueShare":"Fixed percentage agreed per publisher; not public","paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"AnyMind IR FAQ","url":"https://anymindgroup.com/ir/faq/"},{"label":"AnyManager overview","url":"https://anymindgroup.com/news/press-release/11388/"},{"label":"AnyManager Games launch and SDK","url":"https://anymindgroup.com/news/press-release/anymanagergames-launch/"},{"label":"AnyManager AI reporting and integration overview","url":"https://anymindgroup.com/news/press-release/anymanager-ai-reporting-agent"},{"label":"AnyManager product","url":"https://anymindgroup.com/products/anymanager/"},{"label":"Publisher cases","url":"https://anymindgroup.com/case-study/?business=publishergrowth&pa=2"},{"label":"AnyManager terms","url":"https://anymanager.io/terms-of-service/"}],"minTraffic":null}, + "buysellads-carbon-ads": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Withdrawal balance","value":"$1","note":"Method minimums: $20 PayPal, $50 check, $500 wire; fees may apply.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Earnings timing","value":"30 / 60 days","note":"Posted within 30 days of month-end; withdrawable within 60 days.","sourceUrl":"https://www.carbonads.net/faq"}],"joining":[{"label":"Application","value":"Invite-only; review usually 5–7 business days.","note":"Carbon checks traffic, ad relevance, upkeep, and its own capacity.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Audience","value":"English-speaking developer and design audiences. Demand is strongest in the US, Canada, and Europe.","note":"Other regions depend on demand. There's no fixed country list.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Exclusivity","value":"Carbon must be the only ads on the site, with nothing filling unsold space.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Traffic","value":"Monthly pageviews reviewed; no fixed minimum.","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Content","value":"Sites are reviewed for ad relevance and whether they are actively maintained","sourceUrl":"https://www.carbonads.net/faq"},{"label":"Must connect","value":"Use the Carbon Tag or API","sourceUrl":"https://www.buysellads.com/publishers"}],"revenueShare":null,"paymentTerms":"Earnings posted monthly within 30 days; withdrawal within 60 days","paymentThreshold":"$1, then each payment method's own minimum","paymentMethods":["PayPal","Check","Wire transfer","Open Collective, quarterly by request"],"sources":[{"label":"FAQ","url":"https://www.carbonads.net/faq"},{"label":"Join","url":"https://www.carbonads.net/join"},{"label":"Placement policy","url":"https://www.carbonads.net/placement-policy"},{"label":"Carbon API guide","url":"https://www.carbonads.net/docs"},{"label":"BuySellAds publisher page","url":"https://www.buysellads.com/publishers"},{"label":"About","url":"https://www.carbonads.net/about"}],"minTraffic":null}, + "clickio": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Traffic guide","value":"100,000–1,000,000 monthly pageviews","note":"For the Fully Managed plan; varies by country. Meeting it doesn't guarantee approval.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Smaller sites","value":">$500 monthly ad revenue","note":"Below the traffic guide. You may need to show this for 3 months in a row.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Pay day","value":"Net 60","note":"Some demand partners pay Net 30.","sourceUrl":"https://docs.clickio.com/books/get-started/page/payments-billing"}],"joining":[{"label":"Traffic","value":"100,000 pageviews a month for the UK, France, US, Australia, and Canada; 250,000 for Italy and Spain; 300,000 for the rest of Europe; 500,000 for Portugal; 750,000 for Brazil, Colombia, and Mexico; 1 million for the rest of Latin America and the world.","note":"Clickio calls these a guide, not a strict cutoff.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Revenue alternative","value":"More than $500 a month in ad revenue, if you're below the traffic guide","note":"Clickio may ask you to show at least $500 in each of three months in a row.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Verification","value":"Six months of Google Analytics reports may be used if other checks aren't available.","note":"This is proof of traffic, not a rule on site age.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Technical","value":"An ads.txt file, an IAB-approved consent tool (CMP), and over 50% viewability.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Visitor location","value":"Sanctioned countries excluded","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Content","value":"Follow AdSense policies and third-party rights rules","note":"Adult, shocking, or racially intolerant content cannot be monetized.","sourceUrl":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"}],"joinSummary":"Pageview guide varies by country, or more than $500 a month in ad revenue","revenueShare":"Varies with each demand partner's rates","paymentTerms":"Net 60; some demand partners pay Net 30","paymentThreshold":"$100 by ACH, eCheck, SEPA, check, or PayPal; $200 by wire; $10 by WebMoney; £100 by Payoneer","paymentMethods":["ACH","eCheck","SEPA","Wire","Check","PayPal","WebMoney","Payoneer"],"sources":[{"label":"Entry standards","url":"https://docs.clickio.com/books/clickio-entry-standards/page/clickio-entry-standards"},{"label":"Payments","url":"https://docs.clickio.com/books/get-started/page/payments-billing"},{"label":"Publisher agreement","url":"https://legal.clickio.com/PublisherAgreement/"},{"label":"How to join Clickio","url":"https://docs.clickio.com/books/get-started/chapter/how-to-join-clickio"}],"minTraffic":null}, + "concert": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Product","value":"An ad exchange (SSP) run by publishers, for premium publisher ad space.","sourceUrl":"https://www.voxmedia.com/2022/6/7/23157157/vox-media-concert-ssp-publisher-led-supply-side-platform-trade-desk-as-an-exclusive-launch-partner/"},{"label":"Application","value":"No public sign-up form or requirements.","note":"Concert's published terms are for advertisers.","sourceUrl":"https://admanager.concert.io/terms"},{"label":"Publisher fit","value":"Launch partners were large media groups. Concert doesn't publish a minimum size.","sourceUrl":"https://www.voxmedia.com/about-vox-media/2018/5/31/17413008/top-digital-publishers-join-concert-to-create-largest-premium-advertising-marketplace-online/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"SSP launch","url":"https://www.voxmedia.com/2022/6/7/23157157/vox-media-concert-ssp-publisher-led-supply-side-platform-trade-desk-as-an-exclusive-launch-partner/"},{"label":"Advertiser terms","url":"https://admanager.concert.io/terms"},{"label":"Concert media kit","url":"https://cdn.vox-cdn.com/uploads/chorus_asset/file/22462899/Media_Kit_April_2021pdf.pdf"},{"label":"Publisher group","url":"https://www.voxmedia.com/about-vox-media/2018/5/31/17413008/top-digital-publishers-join-concert-to-create-largest-premium-advertising-marketplace-online/"}],"minTraffic":null}, + "ethicalads": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Traffic","value":"50,000 monthly pageviews","sourceUrl":"https://www.ethicalads.io/publishers/faq/"},{"label":"Share","value":"70%","note":"From the publisher policy.","sourceUrl":"https://www.ethicalads.io/publisher-policy/"},{"label":"Payment minimum","value":"$50","note":"Paid monthly, aiming for the 15th.","sourceUrl":"https://www.ethicalads.io/publisher-policy/"}],"joining":[{"label":"Traffic","value":"At least 50,000 pageviews a month.","sourceUrl":"https://www.ethicalads.io/publishers/faq/","note":"Developer sites close to it, or growing fast, may get an exception."},{"label":"Audience","value":"Developer-focused: software, OSS, DevOps, security, data science.","sourceUrl":"https://www.ethicalads.io/publisher-guide/"},{"label":"Placement","value":"One EthicalAds placement per page, with no competing ad on the same page.","sourceUrl":"https://www.ethicalads.io/publisher-policy/"},{"label":"Content","value":"Developer-focused sites that meet the publisher policy","note":"The policy sets ad placement and click-through requirements","sourceUrl":"https://www.ethicalads.io/publishers/faq/"},{"label":"Must connect","value":"Install the EthicalAds ad client after applying","sourceUrl":"https://www.ethicalads.io/publisher-guide/"}],"revenueShare":"70%","paymentTerms":"Monthly, aiming for the 15th","paymentThreshold":"$50","paymentMethods":["PayPal","Open Collective","GitHub Sponsors","Stripe Connect/bank transfer"],"sources":[{"label":"FAQ","url":"https://www.ethicalads.io/publishers/faq/"},{"label":"Policy","url":"https://www.ethicalads.io/publisher-policy/"},{"label":"Guide","url":"https://www.ethicalads.io/publisher-guide/"},{"label":"Terms","url":"https://www.ethicalads.io/terms-of-service/"}],"minTraffic":{"amount":50000,"unit":"pageviews"},"joinSummary":"50,000 pageviews a month; developer sites close to it may get an exception"}, + "ezoic": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"New sites need","value":"250,000 monthly active users","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Share","value":"At least 82%","note":"Of net display-ad revenue, unless you sign a separate agreement.","sourceUrl":"https://www.ezoic.com/terms"},{"label":"Payment minimum","value":"$20","note":"From Ezoic's terms.","sourceUrl":"https://www.ezoic.com/terms"}],"joining":[{"label":"Traffic","value":"New sites generally need 250,000 monthly active users. Ezoic counts users, not pageviews or sessions.","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Incubator","value":"For sites below that bar. Up to 20 sites a month, picked on growth, site value, backend quality, and publisher profile.","sourceUrl":"https://support.ezoic.com/kb/article/ezoic-incubator-program"},{"label":"Grandfathering","value":"Sites earning with Ezoic before February 19, 2026 may keep access if Ezoic stays installed. Removing it for more than 7 days can end that.","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Content","value":"Original content in a supported language.","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Google standing","value":"Comply with Google and AdSense policies; AdSense approval is not required for most sites","sourceUrl":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Must connect","value":"Connect Google Analytics to verify traffic, set up Google MCM, and add Ezoic's JavaScript","sourceUrl":"https://support.ezoic.com/kb/article/ezoic-site-requirements-for-monetization"},{"label":"Approval time","value":"Ezoic review usually finishes within 10 days; Google MCM review can take up to 18 days","sourceUrl":"https://support.ezoic.com/kb/article/ezoic-site-requirements-for-monetization"}],"revenueShare":"At least 82% of net display-ad revenue","paymentTerms":"About 35 days after advertiser payment; payment help also describes Net30 and optional longer cycles","paymentThreshold":"$20","paymentMethods":["Wise","PayPal","Payoneer","Check"],"sources":[{"label":"Requirements","url":"https://support.ezoic.com/kb/article/getting-started-ezoics-requirements?id=getting-started-ezoics-requirements&lang=es-ES"},{"label":"Site requirements and review timing","url":"https://support.ezoic.com/kb/article/ezoic-site-requirements-for-monetization"},{"label":"Terms","url":"https://www.ezoic.com/terms"},{"label":"Payment help","url":"https://support.ezoic.com/kb/article/ezoic-payments?lang=en-US"},{"label":"Incubator","url":"https://support.ezoic.com/kb/article/ezoic-incubator-program"},{"label":"Payment help","url":"https://support.ezoic.com/kb/article/ezoic-payment-schedule-and-thresholds"}],"minTraffic":{"amount":250000,"unit":"monthly active users","label":"250,000 monthly active users"},"joinSummary":"250,000 monthly active users for new sites; a small Incubator program for smaller sites"}, + "freestar": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Typical scale","value":"About 1m monthly pageviews","note":"Typical size, not a minimum.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Target-market share","value":"50%","note":"From the US, UK, Canada, New Zealand, and Australia.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Organic audience","value":"50%","note":"Counts social, Google News and Discover, YouTube, Reddit, and syndication, not only search.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"}],"joining":[{"label":"Traffic","value":"Typically about 1 million pageviews a month; sites of any size can apply.","sourceUrl":"https://freestar.com/contact-us/"},{"label":"Geography","value":"At least half of traffic from the US, UK, Canada, New Zealand, or Australia.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Traffic source","value":"At least 50% from organic audiences.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Content","value":"Original, written for people, mostly long-form, and brand-safe.","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Google standing","value":"Good standing with all major ad providers","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Must connect","value":"Provide GA4 access, traffic and revenue history, and demand-partner status","sourceUrl":"https://freestar.com/publisher-quality-requirements/"},{"label":"Approval time","value":"About 3 to 6 weeks from kickoff to launch","note":"From kickoff to launch, not just the review.","sourceUrl":"https://freestar.com/contact-us/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Quality requirements","url":"https://freestar.com/publisher-quality-requirements/"},{"label":"Contact/onboarding","url":"https://freestar.com/contact-us/"}],"minTraffic":null}, + "geniee-ssp": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Payment minimum","value":"$100 PayPal/Payoneer; $200 wire","note":"From Geniee's older terms. Its 2025 terms don't repeat it.","sourceUrl":"https://s.geniee.jp/aladdin/resources/GenieeInternational/terms_geniee_en.pdf"},{"label":"Payment timing","value":"Two months","note":"By the end of the second month after earnings are calculated, per Geniee's 2025 terms.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"}],"joining":[{"label":"Application","value":"Submit publisher signup and accurate site details.","sourceUrl":"https://admin.genieessp.com/app/signup/en_US"},{"label":"Traffic","value":"No traffic minimum published.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"},{"label":"Content","value":"No false, obscene, infringing or illegal material.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"},{"label":"Integration","value":"Install and retain Geniee tags.","sourceUrl":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"}],"revenueShare":null,"paymentTerms":"By the end of the second month after earnings are calculated, once the advertiser pays (2025 terms)","paymentThreshold":"$100 by PayPal or Payoneer; $200 by wire. From older terms; the 2025 terms don't repeat it.","paymentMethods":["Bank transfer","PayPal","Payoneer (older terms only)","Wire transfer (older terms only)"],"sources":[{"label":"English publisher terms, older","url":"https://s.geniee.jp/aladdin/resources/GenieeInternational/terms_geniee_en.pdf"},{"label":"Publisher signup","url":"https://admin.genieessp.com/app/signup/en_US"},{"label":"Publisher terms, 2025","url":"https://geniee.co.jp/doc/terms_of_service_en20250831.pdf"},{"label":"SSP overview","url":"https://en.geniee.co.jp/services/publishers/"}],"minTraffic":null}, + "google-adsense": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Publisher share","value":"80%","note":"For AdSense for Content, after the advertiser's platform fee. About 68% when the ad comes from Google Ads.","sourceUrl":"https://support.google.com/adsense/answer/180195?hl=en"},{"label":"Payment minimum","value":"$100 USD","note":"Equivalent thresholds vary by account currency.","sourceUrl":"https://support.google.com/adsense/answer/1709871?hl=en"},{"label":"Pay day","value":"21st–26th each month","note":"Once your balance passes the minimum and there are no holds.","sourceUrl":"https://support.google.com/adsense/answer/7164701?hl=en"}],"joining":[{"label":"Traffic","value":"No traffic minimum on Google's eligibility checklist.","note":"Google doesn't say outright that there's no minimum.","sourceUrl":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Content","value":"Original, high-quality content and publisher-policy compliance.","sourceUrl":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Applicant","value":"You must be 18 or older, or have an adult apply for you.","sourceUrl":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Review","value":"Usually a few days, sometimes 2 to 4 weeks. Finish setup within 6 months.","sourceUrl":"https://support.google.com/adsense/answer/7402256?hl=en-AG"},{"label":"Visitor location","value":"Sign-up is available in listed countries","sourceUrl":"https://support.google.com/adsense/answer/13402307?hl=en"},{"label":"Google standing","value":"Your account must not have been closed for invalid traffic or policy violations","sourceUrl":"https://support.google.com/adsense/answer/81904?hl=en"},{"label":"Must connect","value":"Access to the site HTML","note":"Add the verification code or ad code and request a site review","sourceUrl":"https://support.google.com/adsense/answer/7584263?hl=en"}],"revenueShare":"80% after advertiser platform fee for AdSense for Content","paymentTerms":"Monthly, between the 21st and 26th, once you pass the minimum and holds clear","paymentThreshold":"$100 USD or account-currency equivalent","paymentMethods":["Check","EFT","SEPA","Wire","PayPal Hyperwallet where available"],"sources":[{"label":"Share","url":"https://support.google.com/adsense/answer/180195?hl=en"},{"label":"Payment threshold","url":"https://support.google.com/adsense/answer/1709871?hl=en"},{"label":"Payment schedule","url":"https://support.google.com/adsense/answer/7164701?hl=en"},{"label":"Eligibility","url":"https://support.google.com/adsense/answer/9724?hl=en"},{"label":"Signup countries","url":"https://support.google.com/adsense/answer/13402307?hl=en"},{"label":"Account restrictions after invalid traffic or policy termination","url":"https://support.google.com/adsense/answer/81904?hl=en"},{"label":"Site review timing","url":"https://support.google.com/adsense/answer/7402256?hl=en"},{"label":"Site verification and review steps","url":"https://support.google.com/adsense/answer/7584263?hl=en"},{"label":"Payment methods","url":"https://support.google.com/adsense/answer/1714397?hl=en-EN"},{"label":"Eligibility","url":"https://support.google.com/adsense/answer/9724/eligibility-requirements-for-adsense?hl=en-uk"},{"label":"Activation and review","url":"https://support.google.com/adsense/answer/7402256?hl=en-AG"}],"minTraffic":null}, + "infolinks": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Traffic","value":"No pageview or visitor minimum","note":"Infolinks' help center says sites of any size can apply.","sourceUrl":"https://sites0001.infolinks.com/support/quality/what-are-the-eligibility-criteria-for-my-site-to-qualify-with-infolinks"},{"label":"Payment minimum","value":"$50 / $100","note":"$50 for most methods; $100 for wire or Western Union.","sourceUrl":"https://sites0001.infolinks.com/support/payments/what-are-my-payment-options"},{"label":"Pay timing","value":"45 days after month-end","note":"Infolinks' help center says 45 days after month-end. Its older 2015 agreement says 45 working days.","sourceUrl":"https://sites0001.infolinks.com/service-agreement"}],"joining":[{"label":"Traffic","value":"No pageview or visitor minimum. Every site is reviewed and can be declined.","sourceUrl":"https://sites0001.infolinks.com/support/quality/what-are-the-eligibility-criteria-for-my-site-to-qualify-with-infolinks"},{"label":"Review","value":"Usually within 48 hours.","sourceUrl":"https://sites0001.infolinks.com/support/account/how-do-i-sign-up-as-an-infolinks-publisher"},{"label":"Content","value":"No illegal, adult/offensive, hateful or infringing content.","sourceUrl":"https://sites0001.infolinks.com/service-agreement"},{"label":"Integration","value":"Install Infolinks JavaScript after approval.","sourceUrl":"https://sites0001.infolinks.com/support/integration/how-do-i-integrate-infolinks-into-my-website"},{"label":"Google standing","value":"Publisher or related entities must not have been terminated by another monetization platform in the prior 24 months","note":"The agreement names Google AdSense as an example.","sourceUrl":"https://sites0001.infolinks.com/service-agreement"}],"revenueShare":"70% for In-Text ads, from the 2015 agreement; other formats vary","paymentTerms":"45 days after month-end in the help center; 45 working days in the 2015 agreement","paymentThreshold":"$50 for most methods; $100 for wire or Western Union","paymentMethods":["PayPal","Bank wire","eCheck","ACH","Payoneer","Western Union"],"sources":[{"label":"Eligibility","url":"https://sites0001.infolinks.com/support/quality/what-are-the-eligibility-criteria-for-my-site-to-qualify-with-infolinks"},{"label":"Publisher agreement (2015)","url":"https://sites0001.infolinks.com/service-agreement"},{"label":"Signup","url":"https://sites0001.infolinks.com/support/account/how-do-i-sign-up-as-an-infolinks-publisher"},{"label":"Integration","url":"https://sites0001.infolinks.com/support/integration/how-do-i-integrate-infolinks-into-my-website"},{"label":"Payments","url":"https://sites0001.infolinks.com/support/payments/what-are-my-payment-options"},{"label":"Payment thresholds","url":"https://sites0001.infolinks.com/support/payments/what-is-the-infolinks-payment-threshold"},{"label":"Payment fees","url":"https://sites0001.infolinks.com/support/payments/do-i-pay-fees-when-receiving-my-infolinks-payment"}],"minTraffic":{"amount":0,"unit":"pageviews"}}, + "journey-by-mediavine": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Sessions to apply","value":"1,000 Tier 1 sessions in 30 days","note":"From Mediavine's current Journey application.","sourceUrl":"https://www.mediavine.com/application-requirements/"},{"label":"Share","value":"70%","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"},{"label":"Pay day","value":"Net 65","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"}],"joining":[{"label":"Traffic","value":"At least 1,000 Tier 1 sessions in 30 days.","sourceUrl":"https://www.mediavine.com/application-requirements/"},{"label":"Setup","value":"Run Grow for at least 30 days; GA4 is optional.","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"},{"label":"Site age","value":"Four months of domain history, from Mediavine's 2024 getting-started guide","sourceUrl":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/","note":"This comes from a 2024 guide. It may not apply to today's application."},{"label":"Visitor location","value":"Journey Tier 1 countries","sourceUrl":"https://www.mediavine.com/application-requirements/","note":"The application names US, Canada, UK and Australia as examples."},{"label":"Content","value":"Original, engaging, human-written, brand-safe, regularly updated content","sourceUrl":"https://www.journeymv.com/affiliates-faq/"},{"label":"Google standing","value":"No active AdSense or Ad Exchange policy issues","sourceUrl":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine/"}],"revenueShare":"70%","paymentTerms":"Net 65","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Current requirements","url":"https://www.mediavine.com/application-requirements/"},{"label":"Approval criteria","url":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine/"},{"label":"Journey setup and terms","url":"https://www.journeymv.com/getting-started-with-journey-by-mediavine/"},{"label":"Journey FAQ","url":"https://www.journeymv.com/affiliates-faq/"},{"label":"Journey publisher page and supported regions","url":"https://get.journeymv.com/start-your-journey-1/"}],"joinSummary":"1,000 Tier 1 sessions in 30 days","minTraffic":null}, + "media-net": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 30","note":"Paid monthly on click revenue Media.net has collected, per its publisher agreement.","sourceUrl":"https://www.media.net/legal/"}],"joining":[{"label":"Traffic source","value":"Mostly organic traffic. No traffic minimum published.","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Content","value":"Original, high-quality, and easy to navigate. Restricted content isn't allowed.","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Ads.txt","value":"An authorized ads.txt or app-ads.txt file is required.","sourceUrl":"https://www.media.net/legal/inventorypolicies/"},{"label":"Prior provider history","value":"No prior suspension or termination by another monetization provider","sourceUrl":"https://www.media.net/legal/","note":"The agreement also asks you to disclose certain past traffic or policy suspensions"}],"revenueShare":null,"paymentTerms":"Net 30, paid monthly on collected click revenue","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher agreement","url":"https://www.media.net/legal/"},{"label":"Inventory policy","url":"https://www.media.net/legal/inventorypolicies/"},{"label":"Publisher page","url":"https://www.media.net/publishers"}],"minTraffic":null}, + "mediavine": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Ad revenue for Official","value":"$5,000","note":"Site ad revenue in the previous calendar year, not the last month or last 12 months.","sourceUrl":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Official tier share","value":"75%","note":"For new Official sites in 2026. Select, Signature, and Premiere pay more; older contracts differ.","sourceUrl":"https://help.mediavine.com/revenue-share"},{"label":"Pay day","value":"Net 65","note":"Processed from month end.","sourceUrl":"https://help.mediavine.com/when-am-i-getting-paid-0"}],"joining":[{"label":"Revenue","value":"At least $5,000 in site ad revenue in the previous calendar year.","sourceUrl":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Content","value":"Original, audience-first, human, and brand-safe.","sourceUrl":"https://help.mediavine.com/mediavine-official"},{"label":"Google standing","value":"Good standing with AdSense and Ad Exchange; no active policy issues.","sourceUrl":"https://help.mediavine.com/mediavine-official"},{"label":"Traffic","value":"No pageview or session minimum for Official. The revenue rule is what counts.","sourceUrl":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Visitor location","value":"No fixed mix; traffic origin is reviewed","note":"Mediavine evaluates traffic location, sources, and demographics","sourceUrl":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine"},{"label":"Must connect","value":"Provide read-only Google Analytics access during review","sourceUrl":"https://prod.mediavine.com/blog/mediavine-application-process-faq/"}],"revenueShare":"75% for new Official sites in 2026; 80% Select, 85% Signature, 90% Premiere and Premiere Plus; older contracts differ","paymentTerms":"Net 65 from month end, processed on the first business day","paymentThreshold":"$100 for most methods; $200 for eCheck or local transfer","paymentMethods":["PayPal","Domestic ACH","Wise","US wire","eCheck/local bank transfer","International wire"],"sources":[{"label":"Mediavine application requirements","url":"https://www.mediavine.com/application-requirements/"},{"label":"Approval criteria, including Journey","url":"https://help.mediavine.com/what-does-it-take-to-get-approved-by-mediavine"},{"label":"2026 programs and revenue eligibility","url":"https://www.mediavine.com/programs/"},{"label":"Revenue share","url":"https://help.mediavine.com/revenue-share"},{"label":"Payment timing","url":"https://help.mediavine.com/when-am-i-getting-paid-0"},{"label":"Payment methods","url":"https://help.mediavine.com/mediavine-payment-methods"},{"label":"Application and Google Analytics access","url":"https://prod.mediavine.com/blog/mediavine-application-process-faq/"},{"label":"Google MCM onboarding","url":"https://help.mediavine.com/google-mcm-what-you-need-to-know"},{"label":"Publisher Path and previous-year revenue","url":"https://help.mediavine.com/programs-and-the-publisher-path-to-growth"},{"label":"Official criteria","url":"https://help.mediavine.com/mediavine-official"}],"joinSummary":"$5,000 in site ad revenue in the prior calendar year","minTraffic":null}, + "mgid": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Visitor routes","value":"3,000 / 90,000","note":"Unique visitors a day, or visitors a month.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Publisher share","value":"65% (Brazil offer)","note":"From MGID's Brazil offer. Elsewhere, your insertion order sets the share.","sourceUrl":"https://pr.mgid.com/monetize"},{"label":"Payment minimum","value":"$50–$1,000","note":"Depends on payment method, in the Brazil offer.","sourceUrl":"https://pr.mgid.com/monetize"}],"joining":[{"label":"Traffic","value":"3,000 unique visitors a day, or 90,000 visitors a month.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Content","value":"10 current, original articles that follow MGID's policy. No adult, torrent, redirect, or thin content.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Geography","value":"MGID's help lists no traffic source or country limits. Social traffic is accepted. MGID works in 70+ languages and 200+ countries.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Review","value":"Usually an email reply within 24 hours. Weekend applications hear back the next business day.","sourceUrl":"https://help.mgid.com/publishers-general-information"},{"label":"Must connect","value":"Install MGID ad-unit code using the Dashboard instructions","sourceUrl":"https://www.mgid.com/services/publishers-tos"}],"revenueShare":"65% in the Brazil offer; elsewhere, your insertion order sets it","paymentTerms":"Net 30 post-payment in the Brazil offer; elsewhere, your insertion order sets it","paymentThreshold":"In the Brazil offer: $50 by Paymaster24; $100 by PayPal or Tipalti; $1,000 by ACH or international transfer","paymentMethods":["Paymaster24","PayPal","Tipalti","ACH","International transfer"],"sources":[{"label":"Publisher information","url":"https://help.mgid.com/publishers-general-information"},{"label":"Publisher terms updated July 2026","url":"https://www.mgid.com/services/publishers-tos"},{"label":"Brazil publisher offer","url":"https://pr.mgid.com/monetize"},{"label":"Publisher program","url":"https://www.mgid.com/publishers"},{"label":"Older publisher requirements, last updated 2020","url":"https://www.mgid.com/services/publisher-requirements"}],"minTraffic":null,"joinSummary":"3,000 unique visitors a day OR 90,000 visitors a month"}, + "monetizemore-pubguru": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Entry options","value":"$5,000 / 2M","note":"$5,000 a month in ad revenue, or 2 million pageviews a month.","sourceUrl":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"Revenue share","value":"Custom performance-based share","note":"Rate not public.","sourceUrl":"https://www.monetizemore.com/frequently-asked-questions/"}],"joining":[{"label":"Traffic","value":"PubGuru Premium: 2 million pageviews a month, or $5,000 a month in ad revenue.","sourceUrl":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"Product fit","value":"Managed yield optimization for larger, growing publishers.","sourceUrl":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"Terms","value":"The share and contract terms are set for each publisher. Lock-in and notice periods are in your signed agreement.","sourceUrl":"https://www.monetizemore.com/frequently-asked-questions/"},{"label":"Must connect","value":"Flexible setup through the publisher's Google Ad Manager and demand partners","sourceUrl":"https://www.monetizemore.com/blog/header-bidding-platforms/"}],"joinSummary":"$5,000 monthly ad revenue OR 2 million monthly pageviews","revenueShare":"A custom, performance-based share; no fixed percentage published","paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"PubGuru Premium requirements","url":"https://www.monetizemore.com/solutions/pubguru-premium/"},{"label":"PubGuru and header-bidding setup","url":"https://www.monetizemore.com/blog/header-bidding-platforms/"},{"label":"MonetizeMore revenue model","url":"https://www.monetizemore.com/blog/ezoic-vs-adsense-vs-monetizemore/"},{"label":"MonetizeMore FAQ","url":"https://www.monetizemore.com/frequently-asked-questions/"},{"label":"Publisher case","url":"https://www.monetizemore.com/case-study/if-publab-can-5x-their-ad-revenue-using-pubguru-so-can-you/"}],"minTraffic":{"amount":2000000,"unit":"pageviews"}}, + "monumetric": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Propel traffic","value":"10,000–80,000 pageviews/month","note":"For 3 months in a row. Other programs need more.","sourceUrl":"https://www.monumetric.com/monumetric-program-overview/"},{"label":"Payment minimum","value":"$10","note":"Balance carries forward if below minimum.","sourceUrl":"https://www.monumetric.com/payment-terms-and-how-you-are-paid/"},{"label":"Pay day","value":"Net 60","note":"Paid monthly within first 10 business days.","sourceUrl":"https://www.monumetric.com/payment-terms-and-how-you-are-paid/"}],"joining":[{"label":"Traffic","value":"10,000 to 80,000 pageviews a month for 3 months in a row, for Propel.","sourceUrl":"https://www.monumetric.com/monumetric-program-overview/"},{"label":"Top markets","value":"At least 50% of traffic from the US, UK, Australia, or Canada.","sourceUrl":"https://www.monumetric.com/what-are-the-requirements-to-work-with-monumetric/"},{"label":"Content","value":"English; no pornography, drugs, hate or copyright violations.","sourceUrl":"https://www.monumetric.com/what-are-the-requirements-to-work-with-monumetric/"},{"label":"Propel setup","value":"WordPress or Blogger, a 300px sidebar, six ad slots, approval from a major ad provider, and a $99 setup fee.","sourceUrl":"https://www.monumetric.com/requirements-for-the-propel-program/"}],"revenueShare":null,"paymentTerms":"Net 60; monthly within first 10 business days","paymentThreshold":"$10","paymentMethods":["US direct deposit","International PayPal"],"sources":[{"label":"Eligibility","url":"https://www.monumetric.com/what-are-the-requirements-to-work-with-monumetric/"},{"label":"Propel requirements","url":"https://www.monumetric.com/requirements-for-the-propel-program/"},{"label":"Google MCM setup","url":"https://www.monumetric.com/what-googles-switch-to-mcm-means-for-publishers/"},{"label":"Payment terms","url":"https://www.monumetric.com/payment-terms-and-how-you-are-paid/"},{"label":"Program overview","url":"https://www.monumetric.com/monumetric-program-overview/"}],"joinSummary":"10,000 pageviews/month for 3 consecutive months (Propel)","minTraffic":{"amount":10000,"unit":"pageviews"}}, + "newor-media": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Catapult","value":"60,000","note":"US pageviews a month; Elevate has no pageview minimum.","sourceUrl":"https://newormedia.com/programs"},{"label":"Premier","value":"420,000","note":"US pageviews a month.","sourceUrl":"https://newormedia.com/programs"},{"label":"Pay timing","value":"Net 30 / Net 45","note":"Newor's FAQ says Net 30. Its platform agreement says Net 45. Your signed contract decides.","sourceUrl":"https://newormedia.com/faqs/what-are-your-payment-terms"}],"joining":[{"label":"Elevate traffic","value":"No pageview minimum; manual review.","sourceUrl":"https://newormedia.com/elevatefaqs"},{"label":"Catapult/Premier","value":"60,000 and 420,000 US pageviews a month.","sourceUrl":"https://newormedia.com/programs"},{"label":"Elevate criteria","value":"An English site with clear navigation, Google policy compliance, at least two ad units a page, a Google Ad Manager account, and exclusivity.","sourceUrl":"https://newormedia.com/elevatefaqs"},{"label":"Language","value":"The Elevate FAQ says sites must be in English. An August 2025 Newor blog post says non-English sites with 50% of traffic from the US, UK, Canada, or Australia may be accepted.","sourceUrl":"https://newormedia.com/blog/ad-monetization-onboarding-with-newor-media/"},{"label":"Visitor location","value":"Traffic origin is reviewed; no fixed country share stated","sourceUrl":"https://newormedia.com/elevatefaqs"},{"label":"Approval time","value":"Usually responds within 24 to 48 hours","note":"That's when they reply, not when you're approved.","sourceUrl":"https://newormedia.com/elevatefaqs"}],"revenueShare":null,"paymentTerms":"Net 30 in the FAQ; Net 45 in the platform agreement","paymentThreshold":null,"paymentMethods":["PayPal","Payoneer","Direct wire transfer"],"sources":[{"label":"Programs","url":"https://newormedia.com/programs"},{"label":"Elevate FAQ","url":"https://newormedia.com/elevatefaqs"},{"label":"Platform agreement","url":"https://newormedia.com/node/296"},{"label":"Payment terms FAQ","url":"https://newormedia.com/faqs/what-are-your-payment-terms"},{"label":"Payment methods FAQ","url":"https://newormedia.com/faqs/how-do-you-make-payments"},{"label":"2025 onboarding blog","url":"https://newormedia.com/blog/ad-monetization-onboarding-with-newor-media/"}],"joinSummary":"Elevate: no pageview minimum, but manual review, English only, Google Ad Manager, and exclusivity","minTraffic":{"amount":0,"unit":"pageviews","label":"Elevate has no pageview minimum; individual review"}}, + "nitropay": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Net 7","sourceUrl":"https://nitropay.com/docs/"},{"label":"Payout minimum","value":"None","note":"above transaction fees","sourceUrl":"https://nitropay.com/docs/"},{"label":"Go-live time","value":"5–7 business days","note":"after your site is accepted","sourceUrl":"https://nitropay.com/docs/"}],"joining":[{"label":"Traffic","value":"Traffic amount, pageviews and sessions are reviewed; no numeric minimum is published","sourceUrl":"https://nitropay.com/docs/"},{"label":"Visitor location","value":"Traffic geography is reviewed; accepted regions are not listed","sourceUrl":"https://nitropay.com/docs/"},{"label":"Content","value":"Site content and niche are reviewed; guidelines bar adult, illegal, violent, or infringing content","sourceUrl":"https://blog.nitropay.com/guidelines/"},{"label":"Must connect","value":"Install Nitro ad tags and provide a Google Ad Manager network code for Google MCM; GA4 access can speed review","sourceUrl":"https://nitropay.com/docs/"},{"label":"Approval time","value":"Review time isn't published. After acceptance, going live takes 5 to 7 business days on average.","sourceUrl":"https://nitropay.com/docs/"}],"revenueShare":null,"paymentTerms":"Net 7; paid seven days after the start of each month","paymentThreshold":"No fixed minimum; earnings must exceed the transaction fee","paymentMethods":["ACH (US)","EFT (Canada)","FPS (UK)","NPP (Australia)","BECS (New Zealand)","FPSHK (Hong Kong)","SEPA (Europe)","IACH","SWIFT wire"],"sources":[{"label":"Nitro Docs Archive: approval, onboarding, and payments","url":"https://nitropay.com/docs/"},{"label":"Publisher Guidelines","url":"https://blog.nitropay.com/guidelines/"},{"label":"Publisher application form","url":"https://nitropay.com/publishers/"}],"minTraffic":null}, + "opti-digital": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day, Managed Inventory","value":"Net 45","sourceUrl":"https://knowledge-base.optidigital.com/knowledge/understanding-your-payment-terms"},{"label":"Pay day, Managed Account","value":"Net 60","note":"for Opti demand; Google pays its own revenue on a 25-day cycle","sourceUrl":"https://knowledge-base.optidigital.com/knowledge/understanding-your-payment-terms"}],"joining":[{"label":"Traffic","value":"No universal numeric minimum published","sourceUrl":"https://optidigital.com/request-demo/"},{"label":"Must connect","value":"Demand Hub needs CMP, ads.txt, and bidder setup","note":"This is for Demand Hub only. Opti Digital doesn't publish general sign-up rules.","sourceUrl":"https://knowledge-base.optidigital.com/knowledge/getting-started-with-demand-hub"},{"label":"Referral offer","value":"For referrals only: tags live for 3 months on most traffic, an eligible country, good-quality ad space, 3 ads a page, and a registered company","sourceUrl":"https://optidigital.com/refer-a-publisher/"}],"revenueShare":"Share of gross programmatic revenue; percentage not public","paymentTerms":"Net 45 for Managed Inventory; Net 60 for Opti demand under Managed Account; Google pays direct on its 25-day cycle","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Payment terms by setup","url":"https://knowledge-base.optidigital.com/knowledge/understanding-your-payment-terms"},{"label":"Revenue-share and invoice flow","url":"https://knowledge-base.optidigital.com/knowledge/how-is-the-invoicing-working-at-opti-digital-when-will-i-get-paid"},{"label":"Demand Hub publisher integration","url":"https://knowledge-base.optidigital.com/knowledge/getting-started-with-demand-hub"},{"label":"Opti Digital publisher referral page (referral terms only)","url":"https://optidigital.com/refer-a-publisher/"},{"label":"Publisher monetization review","url":"https://optidigital.com/request-demo/"}],"minTraffic":null}, + "playwire": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Traffic to join","value":"500,000","note":"monthly website pageviews; apps need 1,000 daily active users","sourceUrl":"https://www.playwire.com/faq"},{"label":"Your share","value":"50% / 75%","note":"of net direct-sales / indirect-sales revenue","sourceUrl":"https://www.playwire.com/terms-of-service"},{"label":"Pay day","value":"Net 60","sourceUrl":"https://www.playwire.com/terms-of-service"}],"joining":[{"label":"Traffic","value":"500,000 pageviews a month, or 1,000 daily active users for apps.","note":"Playwire's qualification page suggests applying at around 100,000 pageviews to get full value. It doesn't call that a minimum.","sourceUrl":"https://www.playwire.com/faq"},{"label":"Visitor location","value":"At least 20% US traffic is linked to higher earnings.","note":"Playwire doesn't make it a requirement.","sourceUrl":"https://www.playwire.com/qualification-criteria"},{"label":"Content","value":"Unique, original content; invalid traffic (IVT) no higher than 7%","sourceUrl":"https://www.playwire.com/faq"},{"label":"Google standing","value":"Not blacklisted by Google or other major ad providers","sourceUrl":"https://www.playwire.com/faq"},{"label":"Must connect","value":"Implement the Playwire SDK","note":"Required to access Playwire demand","sourceUrl":"https://www.playwire.com/faq"},{"label":"Application","value":"The application has separate paths for websites, mobile apps, and desktop apps.","sourceUrl":"https://www.playwire.com/apply"}],"revenueShare":"50% of net direct-sales revenue; 75% of net indirect-sales revenue","paymentTerms":"Net 60 after month-end; paid in US dollars","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher FAQ","url":"https://www.playwire.com/faq"},{"label":"Qualification criteria","url":"https://www.playwire.com/qualification-criteria"},{"label":"Publisher terms of service","url":"https://www.playwire.com/terms-of-service"},{"label":"Apply Now page","url":"https://www.playwire.com/apply"}],"minTraffic":{"amount":500000,"unit":"pageviews"},"joinSummary":"Websites: 500,000 monthly pageviews; apps: 1,000 daily active users"}, + "pubfuture": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Traffic to join","value":"100,000","note":"monthly pageviews","sourceUrl":"https://pubfuture.com/home"},{"label":"Pay day","value":"Net 30","note":"January earnings are released at the end of February.","sourceUrl":"https://pubfuture.com/home"}],"joining":[{"label":"Traffic","value":"At least 100,000 pageviews a month","sourceUrl":"https://pubfuture.com/home"},{"label":"Content","value":"Safe content with no nudity","sourceUrl":"https://pubfuture.com/home"},{"label":"Must connect","value":"Add PubFuture lines to ads.txt; tags work inside Google Ad Manager and other ad servers","sourceUrl":"https://pubfuture.com/home"},{"label":"Application","value":"Create a publisher account and submit the website for review","sourceUrl":"https://ssp.pubfuture.com/auth/signup"}],"revenueShare":null,"paymentTerms":"Net 30; January earnings released at the end of February","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher FAQ","url":"https://pubfuture.com/home"},{"label":"H5 game vendor case study","url":"https://blog.pubfuture.com/the-secret-to-400-more-revenue-for-h5-game-sites-pubfutures-winning-formula/1007/"},{"label":"Publisher contact form","url":"https://pubfuture.com/contact"},{"label":"Publisher onboarding and ads.txt article","url":"https://blog.pubfuture.com/ads-txt-and-ad-revenue-what-every-publisher-should-know/1236/"},{"label":"Publisher sign-up form","url":"https://ssp.pubfuture.com/auth/signup"}],"minTraffic":{"amount":100000,"unit":"pageviews"}}, + "pubgalaxy": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"PubGalaxy no longer takes publishers. Its publishers moved to Improve Digital.","sourceUrl":"https://www.azerion.com/azerion-unifies-its-publisher-supply-business-under-improve-digital-launching-a-full-circle-solution-for-publishers/"},{"label":"Application","value":"Apply through Improve Digital instead. It has separate access requests for Yield and SSP.","sourceUrl":"https://improvedigital.com/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Azerion announcement unifying publisher supply under Improve Digital","url":"https://www.azerion.com/azerion-unifies-its-publisher-supply-business-under-improve-digital-launching-a-full-circle-solution-for-publishers/"},{"label":"Current Improve Digital publisher offering","url":"https://improvedigital.com/"},{"label":"PubGalaxy official LinkedIn transition update","url":"https://gl.linkedin.com/company/pubgalaxy"}],"minTraffic":null,"joinSummary":"No longer open; moved to Improve Digital"}, + "publift": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Revenue to join","value":"More than $2,000","note":"monthly ad revenue, in USD","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Revenue split","value":"80/20","note":"from a 2026 Publift comparison post; your order sets the actual split","sourceUrl":"https://www.publift.com/blog/best-mediavine-alternatives"},{"label":"Payment minimum","value":"$200","sourceUrl":"https://www.publift.com/blog/the-best-cpm-ad-network-for-publishers"}],"joining":[{"label":"Traffic","value":"500,000 pageviews a month counts, but revenue matters more.","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Visitor location","value":"Best fit: majority of traffic from the Americas, Europe, or Australia/New Zealand","note":"Publift says it works best with this mix. It doesn't set a strict country rule.","sourceUrl":"https://www.publift.com/how-it-works"},{"label":"Content","value":"Useful, original, appropriate, and brand-safe content","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Approval time","value":"Ad tags provided within five days","note":"Publift doesn't say how much of that is review time.","sourceUrl":"https://www.publift.com/blog/the-best-cpm-ad-network-for-publishers"},{"label":"Ad revenue","value":"More than $2,000 a month","note":"The main rule, in USD.","sourceUrl":"https://www.publift.com/blog/publift-requirements"},{"label":"Application","value":"The enquiry form asks for monthly revenue, monthly pageviews, and where most traffic comes from.","sourceUrl":"https://www.publift.com/lets-talk"}],"revenueShare":"80/20, from a 2026 Publift comparison post; your order sets the actual split","paymentTerms":"Google revenue: Net 30; other ad-network revenue: Net 90","paymentThreshold":"$200 USD","paymentMethods":["Direct bank transfer"],"sources":[{"label":"Publift requirements","url":"https://www.publift.com/blog/publift-requirements"},{"label":"How it works and publisher fit","url":"https://www.publift.com/how-it-works"},{"label":"2026 Mediavine alternatives comparison and revenue split","url":"https://www.publift.com/blog/best-mediavine-alternatives"},{"label":"2026 CPM network payment and tag timing comparison","url":"https://www.publift.com/blog/the-best-cpm-ad-network-for-publishers"},{"label":"2026 payment comparison","url":"https://www.publift.com/blog/ezoic-vs-adsense-vs-publift"},{"label":"Publisher enquiry form","url":"https://www.publift.com/lets-talk"}],"minTraffic":null,"joinSummary":"More than $2,000 monthly ad revenue; pageviews are secondary"}, + "publir": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Programmatic share","value":"85%","note":"publisher share; Publir retains 15%, before applicable service costs","sourceUrl":"https://publir.com/service_agreement"},{"label":"Custom ad share","value":"50/50","note":"publisher and Publir; Net 60","sourceUrl":"https://publir.com/service_agreement"},{"label":"Pro fee","value":"15%","note":"separate from custom ad sales","sourceUrl":"https://publir.com/app/pricing"}],"joining":[{"label":"Traffic","value":"No traffic limit on the pricing page.","sourceUrl":"https://publir.com/app/pricing","note":"Publir doesn't publish all of its acceptance rules."},{"label":"Must connect","value":"Install Publir's software and ad tags","sourceUrl":"https://publir.com/service_agreement"},{"label":"Exclusivity","value":"Non-exclusive service agreement","sourceUrl":"https://publir.com/service_agreement"},{"label":"Notice","value":"Either party may end it with 30 days' notice","sourceUrl":"https://publir.com/service_agreement"}],"revenueShare":"Programmatic and subscriptions: 85% to publisher before service costs; custom ads: 50/50; crowdfunding: Publir retains 15%","paymentTerms":"Programmatic and custom ads: at least monthly on Net 60; subscriptions: paid at least monthly","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Pricing","url":"https://publir.com/app/pricing"},{"label":"Service agreement","url":"https://publir.com/service_agreement"}],"minTraffic":null}, + "publisher-collective": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Daily revenue they look for","value":"About €300","note":"A day. A guide, not a cutoff.","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Payment minimum","value":"€500","sourceUrl":"https://www.publisher-collective.com/partners-terms-conditions"}],"joining":[{"label":"Traffic","value":"Core niches are reviewed individually; other niches need 3,000,000 pageviews a month","note":"Core niches are tech, gaming, education, sports, and health. Hobbies are on the niche list, but it's not clear if they count as core.","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Visitor location","value":"Works with publishers globally","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Content","value":"Unique, high-quality content and reputable traffic","note":"No bots or fraudulent traffic; partner terms also prohibit specified content","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Must connect","value":"Add Publisher Collective JavaScript tags to the site","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Approval time","value":"Decision time isn't public. Setup takes 10 to 14 days, and reaching full earnings can take up to 30 days.","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Niches","value":"Technology, video gaming, hobbies and casual games, knowledge and education, sports, health and wellbeing","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Ad revenue","value":"About €300 a day is a guide. Sites earning less may still be accepted for growth or quality.","sourceUrl":"https://www.publisher-collective.com/faq"},{"label":"Application","value":"Send a partner enquiry. A sales team reviews it.","sourceUrl":"https://www.publisher-collective.com/publisher-contact"}],"revenueShare":"Set individually in the Advertising Representation Agreement","paymentTerms":"Paid after the advertiser pays; the partner terms set no fixed interval","paymentThreshold":"€500","paymentMethods":["Bank transfer; GBP, EUR, or USD"],"sources":[{"label":"Publisher FAQ and current eligibility","url":"https://www.publisher-collective.com/faq"},{"label":"Partner terms","url":"https://www.publisher-collective.com/partners-terms-conditions"},{"label":"Publisher contact form","url":"https://www.publisher-collective.com/publisher-contact"}],"joinSummary":"Core genres are reviewed individually; other niches need 3 million pageviews a month","minTraffic":null}, + "pubnation": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"PubNation no longer takes publishers. Its sites have moved to Mediavine.","sourceUrl":"https://help.mediavine.com/"},{"label":"Application","value":"Closed. Apply to Mediavine if you qualify.","sourceUrl":"https://help.mediavine.com/"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Official publisher homepage","url":"https://www.pubnation.com/"},{"label":"Mediavine migration status","url":"https://help.mediavine.com/"}],"minTraffic":null,"joinSummary":"No longer open; moved to Mediavine"}, + "pubpower": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Old traffic figure","value":"100,000","note":"Monthly visitors, from a help page about three years old. Not a cutoff.","sourceUrl":"https://help.pubpower.io/article/website-requirements"},{"label":"Pay day","value":"Net 0 or Net 30","note":"Net 0 above $3,000 a month in revenue","sourceUrl":"https://help.pubpower.io/article/payment-methods-terms"},{"label":"Payoneer minimum","value":"$50","sourceUrl":"https://help.pubpower.io/article/payment-methods-terms"}],"joining":[{"label":"Traffic","value":"About 100,000 monthly visitors, from a help page last updated about three years ago","note":"It may not be a strict rule today.","sourceUrl":"https://help.pubpower.io/article/website-requirements"},{"label":"Content","value":"Follow PubPower's content policy; no pop-ups or redirects","note":"Policy bars or restricts sexual, explicit, violent, illegal, gambling, and infringing content.","sourceUrl":"https://help.pubpower.io/article/website-requirements"},{"label":"Must connect","value":"Add PubPower ads.txt lines and install its ad tag","sourceUrl":"https://help.pubpower.io/article/quick-start-to-your-monetization"},{"label":"Approval time","value":"Usually 24–48 hours to 2–4 days","note":"The quick-start guide says 24 to 48 hours. The FAQ says 2 to 4 days, longer over weekends.","sourceUrl":"https://help.pubpower.io/article/quick-start-to-your-monetization"},{"label":"Application","value":"Create a PubPower publisher account, add the domain, then wait for PubPower's quality review.","sourceUrl":"https://help.pubpower.io/article/quick-start-to-your-monetization"}],"revenueShare":"Essential publisher revenue share is tailored; Advanced custom bidders have no revenue share","paymentTerms":"Net 0 above $3,000 monthly revenue; otherwise Net 30","paymentThreshold":"Bank: $1,000; PayPal: $100; Payoneer: $50; crypto: BCH $100, BTC $1,000, USDT-ERC20 $1,000","paymentMethods":["Bank transfer","PayPal","Payoneer","Cryptocurrency (BCH, BTC, USDT-ERC20)"],"sources":[{"label":"PubPower publisher packages and revenue-sharing model","url":"https://pubpower.io/"},{"label":"Website requirements","url":"https://help.pubpower.io/article/website-requirements"},{"label":"Content policies","url":"https://help.pubpower.io/article/content-policies"},{"label":"Quick-start and domain review","url":"https://help.pubpower.io/article/quick-start-to-your-monetization"},{"label":"Publisher FAQ and review timing","url":"https://help.pubpower.io/article/getting-started-faqs"},{"label":"Payment methods and terms","url":"https://help.pubpower.io/article/payment-methods-terms"},{"label":"Publisher account registration","url":"https://apps.pubpower.io/register"}],"minTraffic":null}, + "raptive": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Traffic to join","value":"25,000","note":"monthly pageviews","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Your share","value":"75%","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360035078152-Getting-Started-with-Raptive"},{"label":"Pay day","value":"Net 45","sourceUrl":"https://help.raptive.com/hc/en-us/articles/202052255-When-do-I-get-paid"}],"joining":[{"label":"Traffic","value":"At least 25,000 pageviews a month","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Visitor location","value":"At least 50% of traffic from five countries at 25,000 to 99,999 pageviews; 40% at 100,000 or more","note":"US, UK, Canada, New Zealand, or Australia","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Content","value":"High-quality, original, human-led content","note":"Sites at 25,000 to 99,999 pageviews need long-form content on most pages.","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Must connect","value":"Google Analytics access","note":"Read-only access","sourceUrl":"https://help.raptive.com/hc/en-us/articles/6681661647515-Applying-to-Raptive-with-Google-Analytics"},{"label":"Domain age","value":"At least six months","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Application","value":"Apply in the Raptive dashboard once you meet the traffic, country share, domain age, and Google Analytics rules.","sourceUrl":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"}],"revenueShare":"75%","paymentTerms":"Net 45; paid on the 15th","paymentThreshold":"$25 for most methods; $100 for wire transfer","paymentMethods":["US direct deposit","eCheck or local bank transfer","PayPal","Paper check","Wire transfer"],"sources":[{"label":"Creator eligibility","url":"https://help.raptive.com/hc/en-us/articles/360032840891-Who-is-eligible-for-Raptive"},{"label":"Applying with Google Analytics","url":"https://help.raptive.com/hc/en-us/articles/6681661647515-Applying-to-Raptive-with-Google-Analytics"},{"label":"Getting started and share","url":"https://help.raptive.com/hc/en-us/articles/360035078152-Getting-Started-with-Raptive"},{"label":"Payment timing","url":"https://help.raptive.com/hc/en-us/articles/202052255-When-do-I-get-paid"},{"label":"Payment methods","url":"https://help.raptive.com/hc/en-us/articles/360013465192-Payment-Methods"},{"label":"Payment thresholds","url":"https://help.raptive.com/hc/en-us/articles/360025379831-Why-didn-t-I-get-my-Raptive-payment"},{"label":"Raptive application in the publisher dashboard","url":"https://dashboard.raptive.com/"}],"minTraffic":{"amount":25000,"unit":"pageviews"}}, + "refinery89": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Payment minimum","value":"€250 or $250","note":"a month, by region","sourceUrl":"https://refinery89.com/docs/faqs/finance/is-there-a-minimum-threshold-for-getting-paid/"}],"joining":[{"label":"Traffic","value":"No traffic minimum published. Contact the team for a site review.","sourceUrl":"https://refinery89.com/docs/faqs/requirements/am-i-eligible/"},{"label":"Visitor location","value":"Worldwide service availability; no qualifying country share published","sourceUrl":"https://refinery89.com/docs/faqs/about-refinery89/where-does-refinery89-operate-and-offer-its-services/"},{"label":"Must connect","value":"Single Tag for websites or SDK for apps","sourceUrl":"https://refinery89.com/docs/faqs/onboarding/how-can-i-start-working-with-refinery89/"},{"label":"Application","value":"The Get Started form takes a site or app and asks for a pageview range and country.","sourceUrl":"https://refinery89.com/get-started/"}],"revenueShare":null,"paymentTerms":"Ask your Publisher Success Manager; no standard schedule is published","paymentThreshold":"€250 or $250, based on region","paymentMethods":["Bank transfer","eCheck","Wire transfer","PayPal"],"sources":[{"label":"Eligibility","url":"https://refinery89.com/docs/faqs/requirements/am-i-eligible/"},{"label":"Publisher service locations","url":"https://refinery89.com/docs/faqs/about-refinery89/where-does-refinery89-operate-and-offer-its-services/"},{"label":"How to start and connect","url":"https://refinery89.com/docs/faqs/onboarding/how-can-i-start-working-with-refinery89/"},{"label":"Payment threshold","url":"https://refinery89.com/docs/faqs/finance/is-there-a-minimum-threshold-for-getting-paid/"},{"label":"Payment timing","url":"https://refinery89.com/docs/faqs/finance/when-do-publishers-get-paid/"},{"label":"Payment methods","url":"https://refinery89.com/docs/faqs/finance/what-are-the-payment-methods-available/"},{"label":"Publisher and app Get Started form","url":"https://refinery89.com/get-started/"}],"minTraffic":null}, + "revcontent": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Payment minimum","value":"$250","note":"in the agreement; payment methods have their own minimums","sourceUrl":"https://help.revcontent.com/knowledge/publisher-terms"}],"joining":[{"label":"Traffic","value":"No minimum pageviews or traffic; publisher account and every domain are reviewed case by case","sourceUrl":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account"},{"label":"Content","value":"No illegal, infringing, pornographic, obscene, illicit-drug, or unlawful-gambling content","sourceUrl":"https://help.revcontent.com/knowledge/publisher-terms"},{"label":"Must connect","value":"After domain approval, Revcontent sends placement choices and widget code","sourceUrl":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account"},{"label":"Approval time","value":"A human team reviews each application and domain; no timing is stated","sourceUrl":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account"}],"revenueShare":"Set in the applicable account or order terms; no standard public rate","paymentTerms":null,"paymentThreshold":"$250 in the agreement; method minimums are $100 for US ACH, $100 for international PayPal, and $1,000 for international wire","paymentMethods":["ACH (US)","PayPal (international)","Wire transfer (international)"],"sources":[{"label":"Publisher account creation and approval","url":"https://help.revcontent.com/knowledge/creating-a-revcontent-publisher-account"},{"label":"Publisher terms","url":"https://help.revcontent.com/knowledge/publisher-terms"},{"label":"Publisher payment requirements and methods","url":"https://help.revcontent.com/knowledge/publisher-payment-requirements-methods"}],"minTraffic":{"amount":0,"unit":"pageviews"}}, + "setupad": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Self-Serve entry","value":"10M+ monthly visits","note":"For the free Self-Serve plan, not the managed service.","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Business Self-Serve fee","value":"€0.05 per 1,000 ad requests","note":"€50 minimum prepayment","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Enterprise spend","value":"€1,000 minimum monthly spend","note":"50M+ visits; separate self-serve offer","sourceUrl":"https://setupad.com/setupad-self-serve/"}],"joining":[{"label":"Traffic","value":"Self-Serve plans start at 10 million visits a month. The managed service has no clear minimum.","note":"Setupad's own articles give 100,000 a month, but some say visitors and some say sessions.","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Visitor location","value":"Main traffic from the US, UK, Canada, or Australia","sourceUrl":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Content","value":"Follow Setupad's quality policy; low-quality sites are not accepted","sourceUrl":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Must connect","value":"Add ads.txt and use Setupad demand sources, including Google","sourceUrl":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Self-Serve","value":"Needs your own Google Ad Manager, direct SSP deals, a high-quality site, and Setupad's approval","sourceUrl":"https://setupad.com/setupad-self-serve/"},{"label":"Application","value":"The online form is for Self-Serve. It asks for a plan and Google Analytics verification of your domain.","sourceUrl":"https://signup.setupad.com/?lang=en&type=7"}],"revenueShare":"Optional Setupad reseller demand takes 20%; Self-Serve direct demand uses a €0.05 CPM fee","paymentTerms":"Net 60 for published managed terms and optional reseller demand; direct SSP partners pay under their own agreements","paymentThreshold":"$100 for the managed service; no minimum stated for reseller demand","paymentMethods":["Bank transfer","PayPal","Payoneer","Wise","Revolut","Paysera"],"sources":[{"label":"Setupad publisher requirements and payment guidance","url":"https://setupad.com/blog/best-ad-networks-for-publishers/"},{"label":"Setupad Self-Serve plans, requirements and billing","url":"https://setupad.com/setupad-self-serve/"},{"label":"Setupad current platform overview","url":"https://setupad.com/"},{"label":"Setupad SSP guide","url":"https://setupad.com/blog/top-ssps-for-publishers/"},{"label":"Setupad Self-Serve application","url":"https://signup.setupad.com/?lang=en&type=7"}],"minTraffic":null,"joinSummary":"Self-Serve: 10M+ monthly visits; managed service has no clear public minimum"}, + "she-media": {"checkedAt":"2026-10-23","keyNumbers":[{"label":"Traffic to join","value":"50,000+","note":"monthly pageviews","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Your share","value":"75%","note":"of gross open-marketplace programmatic revenue, from SHE Media's March 2023 guide","sourceUrl":"https://www.shemedia.com/member-spotlight/member-guide-for-partners"},{"label":"Pay day","value":"Net 35","sourceUrl":"https://www.shemedia.com/partner-network/faqs"}],"joining":[{"label":"Traffic","value":"At least 50,000 pageviews a month for standard approval","note":"Smaller sites can get a case-by-case review. No lower number is published.","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Visitor location","value":"At least 30% US traffic","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Content","value":"Original, high-quality, brand-safe, human-led content","note":"AI-generated images need a clear disclaimer","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Google standing","value":"Good track record with Google ad products and platforms","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Must connect","value":"Google MCM and ads.txt","sourceUrl":"https://support.shemedia.com/hc/en-us/articles/19361668032027-Onboarding-Guide-for-Wordpress-Sites"},{"label":"Approval time","value":"3–4 weeks for a response","note":"That's when you hear back, not a promised decision.","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Analytics","value":"Google Analytics access","sourceUrl":"https://apply.shemedia.com/"},{"label":"Site age","value":"At least 90 days","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Ad inventory","value":"Exclusive mobile and desktop banner ads","note":"Affiliate links and banners are exceptions","sourceUrl":"https://www.shemedia.com/partner-network/faqs"},{"label":"Application","value":"The form asks for average monthly Google Analytics pageviews, current ad networks, and your platform. Sites built on Wix aren't eligible.","sourceUrl":"https://apply.shemedia.com/"}],"revenueShare":"75% of gross open-marketplace programmatic revenue; up to 75% of direct and private marketplace revenue, from the 2023 Member Guide","paymentTerms":"Net 35 after the month closes","paymentThreshold":"$20, from the 2023 Member Guide","paymentMethods":["US direct deposit/ACH","US check if ACH is unavailable","PayPal for international publishers","Wise/ACH or wire for international publishers if approved"],"sources":[{"label":"Partner FAQ","url":"https://www.shemedia.com/partner-network/faqs"},{"label":"Publisher application","url":"https://apply.shemedia.com/"},{"label":"WordPress onboarding guide","url":"https://support.shemedia.com/hc/en-us/articles/19361668032027-Onboarding-Guide-for-Wordpress-Sites"},{"label":"Blogger onboarding guide","url":"https://support.shemedia.com/hc/en-us/articles/30950543170971-Onboarding-Guide-for-Blogger-Sites"},{"label":"Member Guide, updated March 2023","url":"https://www.shemedia.com/member-spotlight/member-guide-for-partners"},{"label":"International payment options, updated April 2025","url":"https://support.shemedia.com/hc/en-us/articles/35987097977627-Payment-Options-for-Publishers-Outside-the-US"}],"joinSummary":"50,000 pageviews a month; smaller sites need individual review","minTraffic":{"amount":50000,"unit":"pageviews"}}, + "snack-media": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Referral program traffic","value":"1 million","note":"impressions, only for the referral program; not a general minimum","sourceUrl":"https://www.snack-media.com/referral/"},{"label":"Notice to leave","value":"30 days","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"}],"joining":[{"label":"Traffic","value":"No traffic minimum in the 2025 publisher agreement. The 1 million impressions rule is only for the referral program.","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"},{"label":"Content","value":"No hateful, pornographic, illegal, or infringing content","note":"The referral program also excludes sensitive and gambling sites","sourceUrl":"https://www.snack-media.com/brand-safety-policy/"},{"label":"Google standing","value":"Must comply with Google policies for the referral program","sourceUrl":"https://www.snack-media.com/referral/"},{"label":"Must connect","value":"Share live traffic stats and install network tags","note":"Tags must be live within 14 days of the agreement date","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"},{"label":"Exclusivity","value":"The standard agreement gives Snack exclusive rights for its listed services and ad sales. The details are on your contract's cover sheet.","sourceUrl":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"}],"revenueShare":null,"paymentTerms":"Within 30 days after the month when Snack Media receives the advertiser funds","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher referral eligibility","url":"https://www.snack-media.com/referral/"},{"label":"Brand safety policy","url":"https://www.snack-media.com/brand-safety-policy/"},{"label":"2025 publisher agreement terms","url":"https://cdn.snack-media.com/wp-content/uploads/2025/08/Snack-Ad-Network-Agreement-TCs.pdf"},{"label":"Terms page","url":"https://www.snack-media.com/Ad_Network_Terms_and_Conditions_for_Publishers/"},{"label":"Snack publisher services","url":"https://www.snack-media.com/publishers/"}],"minTraffic":null}, + "sovrn": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Visits to join","value":"More than 100,000","note":"visits a month, strictly more than 100,000","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Pay day","value":"Net 45","note":"for most direct publishers; terms may vary","sourceUrl":"https://authcore.atlassian.net/wiki/spaces/KB/pages/2693627510846"},{"label":"Payment minimum","value":"$25","note":"$50 for wire","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-the-minimum-payment-threshold-for-sovrn"}],"joining":[{"label":"Traffic","value":"More than 100,000 visits a month for Ad Exchange","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property","note":"Sovrn counts visits, not sessions."},{"label":"Content","value":"Content published in the last 4 months, privacy and brand-safety policies, and not made for children or only for under-16s","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Must connect","value":"Provide an operational business domain and current ads.txt ownership details; intermediaries also need sellers.json","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Approval time","value":"Depends on external checks and possible manual review; no fixed estimate. File updates often show within about two business days.","sourceUrl":"https://knowledge.sovrn.com/kb/what-is-sovrn-s-approval-process-for-the-ad-exchange"},{"label":"Application","value":"Create a platform account first, then start Ad Exchange onboarding; Sovrn Commerce is a separate product.","sourceUrl":"https://platform.sovrn.com/account/signup"}],"revenueShare":null,"paymentTerms":"Net 45 for most Ad Exchange publishers; individual publisher terms may vary","paymentThreshold":"$25 standard; $50 for wire transfer","paymentMethods":["PayPal","ACH (US)","Wire transfer","eCheck/local transfer","Paper check (US)"],"sources":[{"label":"Property approval requirements","url":"https://knowledge.sovrn.com/kb/what-is-required-for-sovrn-to-approve-my-property"},{"label":"Approval process and timing","url":"https://knowledge.sovrn.com/kb/what-is-sovrn-s-approval-process-for-the-ad-exchange"},{"label":"Current payment methods","url":"https://knowledge.sovrn.com/kb/how-do-i-get-paid-for-the-ad-exchange"},{"label":"Payment thresholds","url":"https://knowledge.sovrn.com/kb/what-is-the-minimum-payment-threshold-for-sovrn"},{"label":"Payment timing guide","url":"https://authcore.atlassian.net/wiki/spaces/KB/pages/2693627510846"},{"label":"Ad Exchange account sign-up","url":"https://platform.sovrn.com/account/signup"}],"minTraffic":{"amount":100000,"unit":"visits","label":"More than 100,000 visits a month"}}, + "sparteo": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"No traffic minimum published.","sourceUrl":"https://corporate.sparteo.com/"},{"label":"Application","value":"Contact the sales team. There's no self-serve sign-up.","sourceUrl":"https://corporate.sparteo.com/contact"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Company and publisher services","url":"https://corporate.sparteo.com/"},{"label":"Publisher technology privacy notice","url":"https://corporate.sparteo.com/services-privacy-notice"},{"label":"Publisher platform case study","url":"https://corporate.sparteo.com/post/w3schools-rpm-uplift-unified-monetisation-stack"},{"label":"Company expansion update","url":"https://corporate.sparteo.com/post/sparteo-strengthens-supply-leadership-to-accelerate-global-expansion"},{"label":"Sparteo company LinkedIn","url":"https://www.linkedin.com/company/sparteo"},{"label":"Publisher contact form","url":"https://corporate.sparteo.com/contact"}],"minTraffic":null}, + "taboola": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Pay day","value":"Within 45 days","note":"after the month ends","sourceUrl":"https://www.taboola.com/policies/publisher-revshare-terms-and-conditions-1-3-US"}],"joining":[{"label":"Traffic","value":"No traffic minimum in Taboola's account guide","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Content","value":"Own or properly license content and follow the Publisher Policy","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Must connect","value":"Install Taboola widget code","sourceUrl":"https://developers.taboola.com/web-integrations/docs/welcome"},{"label":"Approval time","value":"Team responds within 48 hours","note":"That's a reply, not final approval.","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Applicant age","value":"At least 18 years old","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Exclusivity","value":"Exclusive only against other third-party recommendation services under current guide","sourceUrl":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"}],"revenueShare":"Set in the applicable publisher Order; no standard public percentage","paymentTerms":"Within 45 days after month-end; paid in US dollars","paymentThreshold":null,"paymentMethods":["Payoneer (most publishers); exceptions vary by location"],"sources":[{"label":"New publisher account guide","url":"https://pubhelp.taboola.com/hc/en-us/articles/39280014062487-How-To-Get-New-Account"},{"label":"Publisher policy","url":"https://pubhelp.taboola.com/hc/en-us/articles/360033751274-Taboola-Publisher-Policy"},{"label":"Web integration guide","url":"https://developers.taboola.com/web-integrations/docs/welcome"},{"label":"Publisher revenue-share terms","url":"https://www.taboola.com/policies/publisher-revshare-terms-and-conditions-1-3-US"},{"label":"Account setup and payment methods","url":"https://pubhelp.taboola.com/hc/en-us/articles/360003157054-Account-Set-Up"}],"minTraffic":null}, + "teads": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"Partnerships go through sales. No traffic minimum published.","sourceUrl":"https://www.teads.com/media-owners"}],"revenueShare":"Terms vary by contract; revenue-share rates may vary with pageview volume or total revenue","paymentTerms":"Negotiated in the media-partner contract; no standard payment interval is published","paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Media owner partnership","url":"https://www.teads.com/media-owners"},{"label":"2025 annual filing describing publisher agreements and negotiated payment timing","url":"https://www.sec.gov/Archives/edgar/data/1454938/000145493826000015/tead-20251231.htm"}],"minTraffic":null}, + "the-moneytizer": {"checkedAt":"2026-10-22","keyNumbers":[{"label":"Organic audience to join","value":"300,000 unique visitors","note":"organic, in the last 30 days, from the US, UK, and Canada","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Publisher share","value":"At least 70%","note":"of pre-tax advertiser billings","sourceUrl":"https://www.themoneytizer.com/cgu"},{"label":"Payout threshold","value":"€50 or $50","note":"market-specific","sourceUrl":"https://us.themoneytizer.com/category/payments-invoices"}],"joining":[{"label":"Traffic","value":"At least 300,000 organic unique visitors in the 30 days before you register","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Visitor location","value":"Qualifying organic unique visitors must be from the US, UK, or Canada","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Content","value":"Your own domain and server access, enough current content, and none of the restricted categories","sourceUrl":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"Must connect","value":"Install ads.txt and ad tags; use a consent banner for EU traffic","sourceUrl":"https://us.themoneytizer.com/publisherguide"},{"label":"Approval time","value":"A quality review follows website registration; no timing stated","sourceUrl":"https://us.themoneytizer.com/publisherguide"},{"label":"Application","value":"Register an account, add the owned website in the portal, then wait for a quality review.","sourceUrl":"https://us.themoneytizer.com/"}],"revenueShare":"At least 70% of the pre-tax amount billed to advertisers under published Terms","paymentTerms":"An invoice is generated automatically at the threshold and is payable 60 days after its issue date","paymentThreshold":"€50 in the terms; $50 in the US publisher FAQ","paymentMethods":["Bank transfer","PayPal"],"sources":[{"label":"Publisher terms and conditions","url":"https://www.themoneytizer.com/cgu"},{"label":"Publisher guide","url":"https://us.themoneytizer.com/publisherguide"},{"label":"US payments FAQ","url":"https://us.themoneytizer.com/category/payments-invoices"},{"label":"US validation criteria","url":"https://us.themoneytizer.com/faq/validation-criteria"},{"label":"US publisher sign-up page","url":"https://us.themoneytizer.com/"}],"minTraffic":null,"joinSummary":"300,000 unique organic visitors in 30 days from the US, UK, or Canada"}, "venatus": {"checkedAt":"2026-10-22","keyNumbers":[],"joining":[{"label":"Traffic","value":"Sales-led publisher review; no numeric threshold published","sourceUrl":"https://www.venatus.com/contact/for-publishers"},{"label":"Content","value":"Must pass a human audit; prohibited content includes hate speech, adult, illegal filesharing/downloads/streaming or hacking, and promotion of drugs/tobacco/firearms","sourceUrl":"https://www.venatus.com/brand-safety"}],"revenueShare":null,"paymentTerms":null,"paymentThreshold":null,"paymentMethods":[],"sources":[{"label":"Publisher brand-safety standards","url":"https://www.venatus.com/brand-safety"},{"label":"Publisher contact page","url":"https://www.venatus.com/contact/for-publishers"},{"label":"Venatus company site","url":"https://www.venatus.com/"},{"label":"Venatus company LinkedIn","url":"https://www.linkedin.com/company/we-are-venatus"}],"minTraffic":null}, }; diff --git a/scripts/platform-fees/data/directory.json b/scripts/platform-fees/data/directory.json index 008a8f1e..ffad1a5f 100644 --- a/scripts/platform-fees/data/directory.json +++ b/scripts/platform-fees/data/directory.json @@ -2508,7 +2508,7 @@ { "key": "fact-4", "state": "variable", - "value": "Subscriptions are included in the agreement’s in-app purchase schedule: 20% Education, 50% mainland-China Games, and 30% other listed categories/markets. Confirm the current seller-specific agreement.", + "value": "Subscriptions fall under the agreement's in-app purchase rates: 20% Education, 50% mainland-China Games, and 30% other listed categories and markets. These come from Huawei's 2020 agreement template; your own seller agreement may differ.", "source_url": "https://terms-dre.platform.hicloud.com/agreementservice/developer/getAgreementTemplate?agrType=1005&country=ove&language=en_us&version=2020063002", "checked_at": "2026-09-30" }, @@ -2535,7 +2535,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The published fee schedule is a 2020 agreement template; confirm current seller terms and regional deductions.", + "comparison_basis": "The published fee schedule is a 2020 agreement template. Your seller agreement and regional deductions may differ.", "fee_percent": null, "fee_basis": null, "sort_order": 3, @@ -3440,21 +3440,21 @@ { "key": "fact-2", "state": "unverified", - "value": "Developer commission is in the unreadable agreement; the 15% claim is unverified.", + "value": "Skich's distribution agreement sets the commission, but its file wouldn't open, so we couldn't check the 15% figure.", "source_url": "https://developer.skich.store/Skich_DistributionAgreement.pdf", "checked_at": "2026-09-30" }, { "key": "fact-3", "state": "unverified", - "value": "In-app purchase rate is not verifiable from readable official terms.", + "value": "Not public. Skich's readable terms don't list an in-app purchase rate.", "source_url": "https://developer.skich.store/Skich_DistributionAgreement.pdf", "checked_at": "2026-09-30" }, { "key": "fact-4", "state": "unverified", - "value": "Subscription rate is not verifiable from readable official terms.", + "value": "Not public. Skich's readable terms don't list a subscription rate.", "source_url": "https://developer.skich.store/Skich_DistributionAgreement.pdf", "checked_at": "2026-09-30" }, @@ -3485,16 +3485,16 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current developer fee terms are in the Distribution Agreement, but its PDF link is unreadable. Accessible developer pages do not publish the fee schedule.", + "comparison_basis": "Skich's fee terms are in its Distribution Agreement, but the PDF link wouldn't open. Its other developer pages don't list fees.", "fee_percent": null, "fee_basis": null, "sort_order": 10, "card": { "owner": "Skich", "take": { - "value": "Fee not confirmed", + "value": "Fee unknown", "percent": null, - "label": "Agreement inaccessible", + "label": "Agreement won't open", "fact_key": "fact-2" }, "lines": [ @@ -3516,7 +3516,7 @@ ], "groups": [], "tag": "Alternative game store", - "note": "Fee terms are in an agreement file we could not read. Ask Skich for current terms." + "note": "Skich's fee terms are in an agreement file that wouldn't open." } }, { @@ -5081,7 +5081,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Game Jolt confirms a share for premium sticker packs but publishes no percentage. Shop sales credit as Gems, then pay monthly after a 7-business-day hold and $20 threshold; this does not establish a paid-game rate.", + "comparison_basis": "Game Jolt says creators get a share of premium sticker pack sales but doesn't give a percentage. Shop sales are credited as Gems and paid monthly after a 7-business-day hold and a $20 threshold. None of this gives a rate for paid games.", "fee_percent": null, "fee_basis": null, "sort_order": 5, @@ -5802,7 +5802,7 @@ }, { "key": "fact-2", - "value": "Current payout guide says 70% developer share for most submissions. Two official 2024 announcements conflict on whether the 90% one-time PCVR/XR Elite exception ended Dec 31, 2024; current duration is unconfirmed.", + "value": "HTC's current payout guide says developers get 70% for most submissions. Two HTC posts from 2024 disagree on whether the one-time 90% PCVR/XR Elite share ended on Dec 31, 2024, so it's not clear if it still applies.", "state": "variable", "source_url": "https://developer.vive.com/resources/viveport/store-guide/store-submission-guide/english/payout/", "checked_at": "2026-09-30" @@ -5860,14 +5860,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The current payout guide says 70% under the standard model. Official 2024 posts disagree on the 90% PCVR/XR Elite exception end date, so its current status is unclear.", + "comparison_basis": "HTC's current payout guide says 70% under the standard model. Two HTC posts from 2024 disagree on whether the 90% PCVR/XR Elite share ended on Dec 31, 2024.", "fee_percent": null, "fee_basis": null, "sort_order": 2, "card": { "owner": "HTC", "tag": "VR games and apps", - "note": "HTC’s own pages disagree on whether the 90% PC VR share ended in 2024.", + "note": "HTC's own pages disagree on whether the 90% PC VR share ended in 2024.", "take": { "value": "30%", "percent": null, @@ -6561,7 +6561,7 @@ }, { "key": "fact-2", - "value": "Current docs require a review fee for paid solutions and waive it for free solutions, but do not state the current amount. A 2023 Salesforce article quoted $999 per attempt; it is not confirmed as current.", + "value": "Salesforce's current docs charge a review fee for paid solutions and waive it for free ones, but don't give the amount. A 2023 Salesforce article said $999 per attempt.", "state": "not_public", "source_url": "https://developer.salesforce.com/docs/platform/isvforce/guide/security-review-how-it-works.html", "checked_at": "2026-09-30" @@ -6631,7 +6631,7 @@ "lines": [ { "label": "Review fee", - "value": "Paid apps, amount unclear", + "value": "Paid apps, amount not public", "fact_key": "fact-2" }, { @@ -7162,7 +7162,7 @@ "lines": [ { "label": "Listing fee", - "value": "Unclear, terms conflict", + "value": "Sources disagree", "fact_key": "fact-2" }, { @@ -8376,7 +8376,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The vendor page advertises up to 70%, while its FAQ says 50%. Vendor terms calculate share on the discounted net price; payout details also conflict.", + "comparison_basis": "DealMirror's vendor page says up to 70%; its FAQ says 50%. The vendor terms work out the share on the discounted net price. Payout details also differ between pages.", "fee_percent": null, "fee_basis": null, "sort_order": 2, @@ -11484,7 +11484,7 @@ "facts": [ { "key": "fact-1", - "value": "Launch: table 10 GB/mo vs FAQ 20 GB; Grow: table 20 GB/mo vs FAQ 30 GB. Launch is $39/mo or $32.50/mo annually, with 5k orders and $10k sales/year.", + "value": "Launch: SendOwl's plan table says 10 GB a month and its FAQ says 20 GB. Grow: the table says 20 GB a month and the FAQ says 30 GB. Launch is $39/mo or $32.50/mo billed yearly, with 5k orders and $10k sales a year.", "state": "variable", "source_url": "https://www.sendowl.com/pricing", "checked_at": "2026-09-30" @@ -11541,7 +11541,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "USD seller pricing; the official plan table and FAQ conflict on Launch and Grow bandwidth. Payment processing and overages are separate.", + "comparison_basis": "USD seller pricing. SendOwl's plan table and FAQ give different bandwidth for Launch and Grow. Payment processing and overages are separate.", "fee_percent": 0, "fee_basis": "No per-sale SendOwl commission; fixed plan and possible bandwidth overage apply.", "sort_order": 3, @@ -12310,7 +12310,7 @@ "facts": [ { "key": "fact-1", - "value": "Free; Creator $10/mo ($100/yr); Plus $30/mo ($300/yr). Max conflicts: pricing page shows $100/mo and $900/yr ($83.3/mo annual); plan article says $90/mo and $900/yr.", + "value": "Free; Creator $10/mo ($100/yr); Plus $30/mo ($300/yr). Max: Beacons' pricing page shows $100/mo or $900/yr ($83.3/mo billed yearly); its Max plan article says $90/mo or $900/yr.", "state": "variable", "source_url": "https://beacons.ai/i/pricing", "checked_at": "2026-09-30" @@ -12367,14 +12367,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "USD plan pricing; the official Max plan article says $90/mo or $900/yr, while the pricing page also shows $100/mo and $83.3/mo annual alongside $900/yr.", + "comparison_basis": "USD plan pricing. Beacons' Max plan article says $90/mo or $900/yr; its pricing page shows $100/mo, or $900/yr ($83.3/mo).", "fee_percent": null, "fee_basis": null, "sort_order": 9, "card": { "owner": "Beacons", "tag": "Link-in-bio store", - "note": "Beacons’ pricing pages list different prices for the Max plan.", + "note": "Beacons' pricing pages list different prices for the Max plan.", "take": { "value": "9%", "percent": 9, @@ -12571,7 +12571,7 @@ }, { "key": "fact-5", - "value": "Terms last modified 2024-03-11: Thinkific collects/remits tax only for Thinkific Payments; current payment and tax setup is not independently confirmed.", + "value": "From The Leap's terms, last changed March 11, 2024: Thinkific collects and remits tax only for Thinkific Payments.", "state": "variable", "source_url": "https://www.theleap.co/apps/terms-of-service/", "checked_at": "2026-09-30" @@ -12599,7 +12599,7 @@ "card": { "owner": "The Leap", "tag": "Creator storefront", - "note": "Tax handling in The Leap’s terms dates from 2024 and could not be confirmed.", + "note": "Tax handling comes from The Leap's terms, last changed in 2024.", "take": { "value": "0%", "percent": 0, @@ -12701,7 +12701,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "USD seller account; homepage trial ($1 for 14 days) conflicts with account table (first 7 days free). The account table lists $0.05 for orders under $2.", + "comparison_basis": "USD seller account. The PayLoadz homepage offers a trial at $1 for 14 days; its account table says the first 7 days are free. The account table lists $0.05 for orders under $2.", "fee_percent": 0, "fee_basis": "The account fee schedule lists 0% plus $0.05 on orders under $2; the homepage says no transaction fees.", "sort_order": 12, @@ -13214,7 +13214,7 @@ "id": "digital-download-stores-and-marketplaces--sellsn", "section_id": "digital-download-stores-and-marketplaces", "name": "SellSN", - "summary": "Former digital-goods marketplace; current service status is unconfirmed.", + "summary": "Former digital-goods marketplace. It's not clear if it still runs.", "website_url": "https://sellsn.io/", "icon_path": "/platform-fees/icons/sellsn-io.png", "facts": [ @@ -13227,28 +13227,28 @@ }, { "key": "fact-2", - "value": "Current fee terms could not be confirmed after the named operator was deregistered following bankruptcy on Jan 15, 2026.", + "value": "Unknown. The company behind SellSN was deregistered after bankruptcy on Jan 15, 2026.", "state": "unverified", "source_url": "https://www.registrucentras.lt/jar/infleid/download.do?oid=327769", "checked_at": "2026-09-30" }, { "key": "fact-3", - "value": "Could not verify current payment-processing terms because the official seller site returned errors in this review.", + "value": "Unknown. SellSN's seller site returned errors when we checked.", "state": "unverified", "source_url": "https://sellsn.io/", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "Could not verify current marketplace-sale fees because the official seller site returned errors in this review.", + "value": "Unknown.", "state": "unverified", "source_url": "https://sellsn.io/", "checked_at": "2026-09-30" }, { "key": "fact-5", - "value": "Could not verify current tax terms because the official demo terms page returned errors in this review.", + "value": "Unknown. SellSN's terms page also returned errors.", "state": "unverified", "source_url": "https://demo.sellsn.io/terms", "checked_at": "2026-09-30" @@ -13273,16 +13273,16 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The named operator SellSN MB was deregistered after bankruptcy on Jan 15, 2026. The site is currently unreachable and no successor or current fee terms were verified.", + "comparison_basis": "The company behind SellSN, SellSN MB, was deregistered after bankruptcy on Jan 15, 2026. The site is down, and we found no successor or current fees.", "fee_percent": null, "fee_basis": null, "sort_order": 17, "card": { "owner": "SellSN", "tag": "Former digital goods marketplace", - "note": "The company behind SellSN was deregistered after bankruptcy. Current terms could not be confirmed.", + "note": "The company behind SellSN was deregistered after bankruptcy. Its current terms are unknown.", "take": { - "value": "Terms unconfirmed", + "value": "Terms unknown", "percent": null, "label": "Operator deregistered", "fact_key": "fact-2" @@ -13451,35 +13451,35 @@ "facts": [ { "key": "fact-1", - "value": "An official July 2022 release says circle registration was free; current setup terms are unconfirmed.", + "value": "A July 2022 DLsite release says circle registration was free.", "state": "variable", "source_url": "https://media.eisys.co.jp/public/uploads/20220714_doujin90_press.pdf", "checked_at": "2026-09-30" }, { "key": "fact-2", - "value": "July 2022 examples: ¥110 sale → ¥55 creator payout (50% fee); ¥4,400 sale → ¥3,520 payout (20% fee). These are historical examples, not current rates.", + "value": "July 2022 examples: ¥110 sale → ¥55 creator payout (50% fee); ¥4,400 sale → ¥3,520 payout (20% fee). Today's rates may differ.", "state": "variable", "source_url": "https://media.eisys.co.jp/public/uploads/20220714_doujin90_press.pdf", "checked_at": "2026-09-30" }, { "key": "fact-3", - "value": "2020 terms: monthly sales were paid by the following month’s 20th; balances under ¥3,000 rolled over. DLsite covered Japanese bank transfer fees; sellers paid overseas transfer-service charges. Current terms unconfirmed.", + "value": "From DLsite's 2020 terms: sales were paid by the 20th of the following month, and balances under ¥3,000 rolled over. DLsite covered Japanese bank transfer fees; sellers paid overseas transfer charges.", "state": "variable", "source_url": "https://info.eisys.co.jp/dlsite/9e180856065589c9?locale=en_US", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "The 2017 Taiwan terms said English DLsite followed Japan’s pricing, payment, and tax standards. They do not confirm today’s Japanese royalty table.", + "value": "DLsite's 2017 Taiwan terms said English DLsite followed Japan's pricing, payment, and tax standards. They don't show today's Japanese royalty table.", "state": "variable", "source_url": "https://www.dlsite.com.tw/circle_regulations", "checked_at": "2026-09-30" }, { "key": "fact-5", - "value": "2020 terms say non-residents and foreign corporations are subject to withholding. The 2017 Taiwan terms show historical rates; current withholding and invoice rules are unconfirmed.", + "value": "DLsite's 2020 terms say non-residents and foreign companies are subject to withholding. The 2017 Taiwan terms show older rates. Current withholding and invoice rules aren't on DLsite's public pages.", "state": "variable", "source_url": "https://info.eisys.co.jp/dlsite/9e180856065589c9?locale=en_US", "checked_at": "2026-09-30" @@ -13508,14 +13508,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current DLsite circle terms and royalty table could not be confirmed. The latest accessible official fee examples are from July 2022; payout and withholding terms are from 2020, and the Taiwan terms were last updated in 2017.", + "comparison_basis": "DLsite's current circle terms and royalty table aren't on its public pages. The newest official fee examples are from July 2022, payout and withholding terms from 2020, and the Taiwan terms from 2017.", "fee_percent": null, "fee_basis": null, "sort_order": 19, "card": { "owner": "DLsite", "tag": "Japanese digital works", - "note": "DLsite’s share depends on price. These are 2022 examples; current rates are not confirmed.", + "note": "DLsite's share depends on price. These are its July 2022 examples.", "take": { "value": "20–50%", "percent": null, @@ -15935,14 +15935,14 @@ { "key": "fact-2", "state": "variable", - "value": "The August 2025 plan notice says there is no platform transaction fee; current fee terms were not independently confirmed.", + "value": "No platform transaction fee, per PayKickstart's August 2025 plan notice.", "source_url": "https://support.paykickstart.com/changelog/rocket-introducing-new-paykickstart-pricing-plans-all-features-no-tiers", "checked_at": "2026-09-30" }, { "key": "fact-3", "state": "variable", - "value": "The published fee table lists Connect at 2.80% + $0.25 through $5,000 prior-month volume, then lower tiers. Another current guide says Connect 2.0 is still in development and recommends other gateways.", + "value": "PayKickstart's fee table lists Connect at 2.80% + $0.25 up to $5,000 in prior-month volume, then lower tiers. Another PayKickstart guide says Connect 2.0 is still in development and suggests other gateways.", "source_url": "https://support.paykickstart.com/en/articles/0071362-connect-payment-gateway-fees-disclosure", "checked_at": "2026-09-30" }, @@ -15973,14 +15973,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The August 2025 plan notice lists $79/month, a 14-day trial, and no platform transaction fee; current pricing was not confirmed. The published Connect table lists a $5,000 prior-month tier cap, while another current guide says Connect 2.0 is still in development and recommends other gateways.", + "comparison_basis": "PayKickstart's August 2025 plan notice lists $79/month, a 14-day trial, and no platform transaction fee. Its Connect table lists a $5,000 prior-month tier cap, while another PayKickstart guide says Connect 2.0 is still in development and suggests other gateways.", "fee_percent": null, "fee_basis": null, "sort_order": 14, "card": { "owner": "PayKickstart", "tag": "Checkout pages", - "note": "These figures come from an August 2025 notice. Current prices could not be confirmed.", + "note": "These figures come from PayKickstart's August 2025 plan notice.", "take": { "value": "0%", "percent": null, @@ -16074,7 +16074,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Default shop share is 50% of list price; shop/product rates may vary. Enterprise-specific rates could not be confirmed from accessible current official terms.", + "comparison_basis": "Default shop share is 50% of list price; shop and product rates may vary. Enterprise rates aren't in Creative Market's public terms.", "fee_percent": null, "fee_basis": null, "sort_order": 0, @@ -16196,7 +16196,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current seller page advertises 50% default single-sale share and 75% for seller-referred sales. Subscription earnings follow the July 2025 guide; latest posted Designer Terms expired 9 Apr 2026, so current deductions are not confirmed.", + "comparison_basis": "Creative Fabrica's seller page says 50% on single sales by default and 75% on sales you refer. Subscription earnings follow the July 2025 guide. The latest posted Designer Terms expired on 9 Apr 2026, so current deductions aren't stated.", "fee_percent": null, "fee_basis": null, "sort_order": 1, @@ -16953,7 +16953,7 @@ }, { "key": "fact-5", - "value": "Refunds and chargebacks can reverse royalties; tax withholding depends on country and treaty. Terms list a $10 payout threshold, then $10 for non-SWIFT and $20 for SWIFT, so the threshold wording conflicts.", + "value": "Refunds and chargebacks can reverse royalties; tax withholding depends on country and treaty. Canva's terms give a $10 payout threshold, then say $10 for non-SWIFT and $20 for SWIFT payouts.", "state": "variable", "source_url": "https://www.canva.com/policies/contributor-agreement/", "checked_at": "2026-09-30" @@ -17200,7 +17200,7 @@ }, { "key": "fact-5", - "value": "Net revenue deducts set expenses, which can include taxes, fees, disputes, chargebacks, and refunds. PayPal payout starts at $25 and is due by the end of the following month. The creator page still says 40% of shared subscription/API revenue, unlike the terms.", + "value": "Net revenue deducts set expenses, which can include taxes, fees, disputes, chargebacks, and refunds. PayPal payout starts at $25 and is due by the end of the following month. The creator page still says 40% of shared subscription and API revenue, which differs from the terms.", "state": "variable", "source_url": "https://thenounproject.com/legal/creator-terms/icons/", "checked_at": "2026-09-30" @@ -17225,14 +17225,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current creator terms set different shares for single purchases and subscription/API use; a creator page still shows a conflicting subscription rate.", + "comparison_basis": "Current creator terms set different shares for single purchases and subscription or API use. A creator page still shows a different subscription rate.", "fee_percent": null, "fee_basis": null, "sort_order": 11, "card": { "owner": "Noun Project", "tag": "Icons", - "note": "The creator page still says creators get 40% of subscriptions, which conflicts with the terms.", + "note": "The creator page still says creators get 40% of subscriptions, which differs from the terms.", "take": { "value": "40%", "percent": 40, @@ -17302,14 +17302,14 @@ "facts": [ { "key": "fact-1", - "value": "New contributor intake is closed. Current earnings for existing accounts are not stated on the public closed-registration page.", + "value": "New contributor sign-ups are closed. The public page doesn't list earnings for existing accounts.", "state": "not_public", "source_url": "https://contributor.flaticon.com/closed", "checked_at": "2026-09-30" }, { "key": "fact-2", - "value": "The only public contributor agreement located is dated 2022; it cannot confirm current rates for existing accounts.", + "value": "The only public contributor agreement is from 2022. Today's rates for existing accounts aren't public.", "state": "not_public", "source_url": "https://contributor.flaticon.com/terms/terms_20221110.pdf", "checked_at": "2026-09-30" @@ -17323,14 +17323,14 @@ }, { "key": "fact-4", - "value": "Current exclusivity terms for existing accounts are not published on the accessible public contributor pages.", + "value": "Flaticon's public contributor pages don't list current exclusivity terms for existing accounts.", "state": "not_public", "source_url": "https://contributor.flaticon.com/", "checked_at": "2026-09-30" }, { "key": "fact-5", - "value": "Current deductions and payout terms for existing accounts are not published on the accessible public contributor pages.", + "value": "Flaticon's public contributor pages don't list current deductions or payout terms for existing accounts.", "state": "not_public", "source_url": "https://contributor.flaticon.com/", "checked_at": "2026-09-30" @@ -17351,7 +17351,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "New Flaticon contributor registration is closed; current earnings terms for existing accounts are not confirmed by current public documents.", + "comparison_basis": "New Flaticon contributor sign-ups are closed. Current earnings terms for existing accounts aren't in public documents.", "fee_percent": null, "fee_basis": null, "sort_order": 12, @@ -17423,7 +17423,7 @@ }, { "key": "fact-5", - "value": "Each order item costs $0.30. The same page FAQ says no-referrer Marketplace discovery pays a 15% commission, conflicting with the table’s 3% Pro promotion fee.", + "value": "Each order item costs $0.30. The FAQ on the same page says Marketplace sales with no referrer pay a 15% commission, but the table lists a 3% Pro promotion fee.", "state": "variable", "source_url": "https://help.artstation.com/en/articles/16155081-digital-product-earnings-how-much-will-i-make-from-each-sale", "checked_at": "2026-09-30" @@ -17448,14 +17448,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "US sellers, USD. The August 2026 Help table and Sell Goods page list different shares; the Help article also gives a conflicting 15% no-referrer fee.", + "comparison_basis": "US sellers, USD. ArtStation's August 2026 Help table and its Sell Goods page list different shares. The Help article also gives a 15% fee for sales with no referrer, which differs from its table.", "fee_percent": null, "fee_basis": null, "sort_order": 13, "card": { "owner": "ArtStation", "tag": "Art and digital products", - "note": "Official pages give different rates, from 5% to 30%, plus 30¢ per item. Confirm your rate in your dashboard.", + "note": "ArtStation's pages give different rates, from 5% to 30%, plus 30¢ per item.", "take": { "value": "5–30%", "percent": null, @@ -17859,7 +17859,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "US sellers; commission rates are current support terms. The older participation page advertises a 35% launch bonus not listed in the current commission guide; current applicability is unconfirmed.", + "comparison_basis": "US sellers; commission rates are from current support pages. An older participation page offers a 35% launch bonus that the current commission guide doesn't list, so it may no longer apply.", "fee_percent": null, "fee_basis": null, "sort_order": 1, @@ -18025,7 +18025,7 @@ "id": "fonts--youworkforthem", "section_id": "fonts", "name": "YouWorkForThem", - "summary": "YouWorkForThem licenses fonts from independent designers, but its current creator share, seller costs, rights, and payout terms are not published on accessible public seller pages.", + "summary": "YouWorkForThem licenses fonts from independent designers. Its public pages don't list the creator share, seller costs, rights, or payout terms.", "website_url": "https://www.youworkforthem.com/", "icon_path": "/platform-fees/icons/www-youworkforthem-com.png", "facts": [ @@ -18181,7 +18181,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Default shop share is 50% of list price; shop/product rates may vary. Enterprise-specific rates could not be confirmed from accessible current official terms.", + "comparison_basis": "Default shop share is 50% of list price; shop and product rates may vary. Enterprise rates aren't in Creative Market's public terms.", "fee_percent": null, "fee_basis": null, "sort_order": 4, @@ -18307,7 +18307,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current seller page advertises 50% default single-sale share and 75% for seller-referred sales. Subscription earnings follow the July 2025 guide; latest posted Designer Terms expired 9 Apr 2026, so current deductions are not confirmed.", + "comparison_basis": "Creative Fabrica's seller page says 50% on single sales by default and 75% on sales you refer. Subscription earnings follow the July 2025 guide. The latest posted Designer Terms expired on 9 Apr 2026, so current deductions aren't stated.", "fee_percent": null, "fee_basis": null, "sort_order": 5, @@ -19891,7 +19891,7 @@ }, { "key": "fact-3", - "value": "Envato says Code authors are not part of the current invitation intake.", + "value": "Envato says it isn't inviting new Code authors right now.", "state": "not_applicable", "source_url": "https://help.author.envato.com/hc/en-us/articles/53981083605913-Author-applications-are-paused-new-authors-join-by-invitation-only", "checked_at": "2026-09-30" @@ -20526,14 +20526,14 @@ }, { "key": "fact-3", - "value": "The publisher guide does not specify a separate seller fee, but the linked Distribution Agreement could not be read in this review.", + "value": "The publisher guide doesn't list a separate seller fee. Fab's Distribution Agreement, linked from the guide, wouldn't open when we checked.", "state": "unverified", "source_url": "https://dev.epicgames.com/documentation/fab/publisher-get-started-in-fab", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "The linked Distribution Agreement could not be read in this review, so exclusivity remains unverified.", + "value": "Unknown. The Distribution Agreement that would set this wouldn't open.", "state": "unverified", "source_url": "https://www.fab.com/distribution-agreement", "checked_at": "2026-09-30" @@ -20569,14 +20569,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Publisher guide states an 88% share; the linked Distribution Agreement was not readable in this review, so seller fees and exclusivity remain unverified.", + "comparison_basis": "The publisher guide says 88%. The linked Distribution Agreement wouldn't open, so seller fees and exclusivity are unknown.", "fee_percent": null, "fee_basis": null, "sort_order": 0, "card": { "owner": "Epic", "tag": "3D assets on Fab", - "note": "Fab’s distribution agreement could not be read, so exclusivity is not confirmed.", + "note": "Fab's distribution agreement wouldn't open, so exclusivity is unknown.", "take": { "value": "12%", "percent": 12, @@ -21706,7 +21706,7 @@ }, { "key": "fact-2", - "value": "Official pages disagree: the Aug 2026 earnings guide lists 88% Non-Pro and 92–95% Pro; the seller page lists 70% Free and 80–95% Pro.", + "value": "ArtStation's pages disagree: the Aug 2026 earnings guide lists 88% Non-Pro and 92–95% Pro; the seller page lists 70% Free and 80–95% Pro.", "state": "variable", "source_url": "https://help.artstation.com/en/articles/16155081-digital-product-earnings-how-much-will-i-make-from-each-sale", "checked_at": "2026-09-30" @@ -21727,7 +21727,7 @@ }, { "key": "fact-5", - "value": "The earnings guide deducts $0.30 per item; seller-set contract affiliates take 20–80%. Its FAQ still says 15% promotion while current fee breakdowns say 3%.", + "value": "The earnings guide deducts $0.30 per item; contract affiliates set by the seller take 20–80%. Its FAQ still says 15% for promotion, while current fee breakdowns say 3%.", "state": "variable", "source_url": "https://help.artstation.com/en/articles/16155081-digital-product-earnings-how-much-will-i-make-from-each-sale", "checked_at": "2026-09-30" @@ -21752,14 +21752,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current official pages conflict on marketplace shares. The detailed Aug 2026 help guide lists 88% Non-Pro and 92–95% Pro, while the seller page lists 70% Free and 80–95% Pro.", + "comparison_basis": "ArtStation's pages disagree on marketplace shares. The Aug 2026 help guide lists 88% Non-Pro and 92–95% Pro; the seller page lists 70% Free and 80–95% Pro.", "fee_percent": null, "fee_basis": null, "sort_order": 9, "card": { "owner": "ArtStation", "tag": "3D assets", - "note": "Official pages give different rates, from 5% to 30%, plus 30¢ per item. Confirm your rate in your dashboard.", + "note": "ArtStation's pages give different rates, from 5% to 30%, plus 30¢ per item.", "take": { "value": "5–30%", "percent": null, @@ -22234,7 +22234,7 @@ "facts": [ { "key": "fact-1", - "value": "A 2023 launch post said new stores received one paid-model slot and had a $5 minimum; current creator terms do not confirm whether these limits remain.", + "value": "A 2023 launch post said new stores got one paid-model slot and a $5 minimum. Current creator terms don't say if these limits still apply.", "state": "variable", "source_url": "https://blog.prusa3d.com/printables-store_87810/", "checked_at": "2026-09-30" @@ -22335,7 +22335,7 @@ "facts": [ { "key": "fact-1", - "value": "An older Prusa Help page says creators need level 18 to start a Club; current creator terms do not confirm whether this still applies.", + "value": "An older Prusa help page says creators need level 18 to start a Club. Current creator terms don't say if this still applies.", "state": "variable", "source_url": "https://help.prusa3d.com/article/about_493458", "checked_at": "2026-09-30" @@ -22821,14 +22821,14 @@ }, { "key": "fact-3", - "value": "The publisher guide does not specify a separate seller fee, but the linked Distribution Agreement could not be read in this review.", + "value": "The publisher guide doesn't list a separate seller fee. Fab's Distribution Agreement, linked from the guide, wouldn't open when we checked.", "state": "unverified", "source_url": "https://dev.epicgames.com/documentation/fab/publisher-get-started-in-fab", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "The linked Distribution Agreement could not be read in this review, so exclusivity remains unverified.", + "value": "Unknown. The Distribution Agreement that would set this wouldn't open.", "state": "unverified", "source_url": "https://www.fab.com/distribution-agreement", "checked_at": "2026-09-30" @@ -22860,14 +22860,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Publisher guide states an 88% share; the linked Distribution Agreement was not readable in this review, so seller fees and exclusivity remain unverified.", + "comparison_basis": "The publisher guide says 88%. The linked Distribution Agreement wouldn't open, so seller fees and exclusivity are unknown.", "fee_percent": null, "fee_basis": null, "sort_order": 1, "card": { "owner": "Epic", "tag": "Game assets on Fab", - "note": "Fab’s distribution agreement could not be read, so exclusivity is not confirmed.", + "note": "Fab's distribution agreement wouldn't open, so exclusivity is unknown.", "take": { "value": "12%", "percent": 12, @@ -24340,7 +24340,7 @@ }, { "key": "fact-5", - "value": "Automatic monthly payouts use the account setting, but the payout help does not state its floor. Terms v9 sets $35 for electronic or $500 for checks; some countries also wait 90 days from first approval. The official pages conflict on the minimum.", + "value": "Monthly payouts follow your account setting, but the payout help page doesn't give a minimum. Terms v9 sets $35 for electronic payouts or $500 for checks, and some countries wait 90 days from first approval. Shutterstock's pages don't agree on the minimum.", "state": "variable", "source_url": "https://submit.shutterstock.com/help/en/articles/10617435-how-do-i-get-paid-for-my-work", "checked_at": "2026-09-30" @@ -25313,7 +25313,7 @@ }, { "key": "fact-2", - "value": "Print/product orders: 10% Basic, 9% Standard, or 8% Pro on subtotal excluding shipping/tax; processor fees separate. Image-license fee rate is not confirmed in the accessible schedule.", + "value": "Print and product orders: 10% Basic, 9% Standard, or 8% Pro on the subtotal before shipping and tax; processor fees are separate. The fee schedule doesn't list a rate for image licenses.", "state": "verified", "source_url": "https://support.photoshelter.com/hc/en-us/articles/203373140-Self-Fulfilled-Prints-Products", "checked_at": "2026-09-30" @@ -25351,14 +25351,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The 10/9/8% schedule applies to self-fulfilled print/product orders; current image-license rate is not confirmed in accessible public sources.", + "comparison_basis": "The 10/9/8% schedule covers print and product orders you fulfill yourself. PhotoShelter's public pages don't list a rate for image licenses.", "fee_percent": null, "fee_basis": null, "sort_order": 9, "card": { "owner": "PhotoShelter", "tag": "Photography website", - "note": "Processor fees are extra. License sale fees could not be confirmed.", + "note": "Processor fees are extra. The fee on license sales isn't public.", "take": { "value": "10%", "percent": 10, @@ -26304,7 +26304,7 @@ }, { "key": "fact-5", - "value": "Automatic monthly payouts use the account setting, but payout help does not state its floor. Terms v9 sets $35 electronically or $500 by check; some countries also wait 90 days from first approval. Official pages conflict on the minimum.", + "value": "Monthly payouts follow your account setting, but the payout help page doesn't give a minimum. Terms v9 sets $35 for electronic payouts or $500 for checks, and some countries wait 90 days from first approval. Shutterstock's pages don't agree on the minimum.", "state": "variable", "source_url": "https://submit.shutterstock.com/help/en/articles/10617435-how-do-i-get-paid-for-my-work", "checked_at": "2026-09-30" @@ -26427,7 +26427,7 @@ }, { "key": "fact-3", - "value": "No upfront listing fee is stated. The FAQ says no commission, while the per-item terms give creators 50% or 70%; the meaning of commission is unclear.", + "value": "No upfront listing fee is listed. The FAQ says MotionElements takes no commission, but its per-item terms give creators 50% or 70%, so it's not clear what the FAQ means by commission.", "state": "variable", "source_url": "https://help.motionelements.com/en/contributor/docs/does-motionelements-commission-contributors-works", "checked_at": "2026-09-30" @@ -26478,7 +26478,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Creators receive 50% of per-item non-exclusive sales or 70% of exclusive sales. The help page also says it does not commission contributor works, but its use of commission is unclear; no upfront listing fee is stated.", + "comparison_basis": "Creators receive 50% of per-item non-exclusive sales or 70% of exclusive sales. The help page says it doesn't commission contributor works, but it's not clear what it means by that. No upfront listing fee is listed.", "fee_percent": null, "fee_basis": null, "sort_order": 3, @@ -27314,7 +27314,7 @@ }, { "key": "fact-5", - "value": "Artist page says quarterly statements and payment 45 days after quarter-end once earnings reach $25; PayPal is usual, with Wise, Payoneer, or crypto also listed. Older FAQ fees and wire terms are not confirmed there.", + "value": "The artist page says statements are quarterly, paid 45 days after quarter-end once earnings reach $25. PayPal is usual; Wise, Payoneer, and crypto are also listed. Fees and wire terms from an older FAQ aren't on that page.", "state": "variable", "source_url": "https://www.audiosparx.com/module/alliance/infoAS.cfm", "checked_at": "2026-09-30" @@ -30020,7 +30020,7 @@ }, { "key": "fact-4", - "value": "A global Tipalti guide lists PayPal availability, a $200 USD minimum, $5 e-check/$26 wire fees and possible 3% FX. A 2025 Brazil guide says Tipalti became the only method from May 2025 with a $50 minimum. Current global route and minimum are unclear.", + "value": "ONErpm's global Tipalti guide lists PayPal, a $200 USD minimum, $5 e-check and $26 wire fees, and possible 3% FX. A 2025 Brazil guide says Tipalti became the only method from May 2025, with a $50 minimum. It's not clear which applies outside Brazil.", "state": "variable", "source_url": "https://dashboard.onerpm.com/assets/files/EN_Tipalti_new_payment_solution_Artist_Guide.pdf", "checked_at": "2026-09-30" @@ -30587,7 +30587,7 @@ }, { "key": "fact-3", - "value": "English plan page: Lite retains 90%; Core and Max retain 100%. The official pt-BR page says Core retains 90%; the royalty share is inconsistent across current localized pages.", + "value": "SoundOn's English plan page says Lite keeps 90% and Core and Max keep 100%. Its Portuguese (Brazil) page says Core keeps 90%.", "state": "variable", "source_url": "https://www.soundon.global/?lang=en", "checked_at": "2026-09-30" @@ -30626,7 +30626,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Lite keeps 90%; Max 100%; current English and pt-BR Core pages disagree.", + "comparison_basis": "Lite keeps 90%; Max 100%. SoundOn's English and Portuguese (Brazil) pages disagree on Core.", "fee_percent": 10, "fee_basis": "Lite streaming earnings", "sort_order": 14, @@ -31106,7 +31106,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "GBP. Artist subscriptions cover unlimited releases; pay-per-release is also offered. Current FAQ lists Convera or PayPal and a £10 minimum outside the UK; the March 2026 page lists a £3 fee and Western Union, conflicting with newer terms. UK minimum and fees are not published.", + "comparison_basis": "GBP. Artist subscriptions cover unlimited releases; pay-per-release is also offered. The current FAQ lists Convera or PayPal and a £10 minimum outside the UK. A March 2026 page lists a £3 fee and Western Union, which differs from the newer terms. The UK minimum and fees aren't public.", "fee_percent": 0, "fee_basis": "Standard artist distribution royalty fee", "sort_order": 18, @@ -31465,7 +31465,7 @@ }, { "key": "fact-5", - "value": "Current FAQ lists a C$50 payout threshold. The service agreement, last updated in 2012, also lists price ceilings, price-matching rules, and withholding until $100; current applicability is unconfirmed.", + "value": "The current FAQ lists a C$50 payout threshold. The service agreement, last updated in 2012, also lists price ceilings, price-matching rules, and withholding until $100. Those 2012 rules may no longer apply.", "state": "variable", "source_url": "https://www.kobo.com/kobo-writing-life/blog/frequently-asked-questions", "checked_at": "2026-09-30" @@ -32677,41 +32677,41 @@ "id": "ebooks-and-print-books--xinxii", "section_id": "ebooks-and-print-books", "name": "XinXii", - "summary": "eBook and audiobook distribution; current author terms could not be confirmed from accessible official pages.", + "summary": "eBook and audiobook distribution. Its official pages wouldn't load, so current author terms are unknown.", "website_url": "https://www.xinxii.com/", "icon_path": "/platform-fees/icons/www-xinxii-com.png", "facts": [ { "key": "fact-1", - "value": "The 2021 Apple listing describes ebook and audiobook distribution; current service status is unverified.", + "value": "A 2021 Apple listing describes ebook and audiobook distribution. It's not clear if the service still runs.", "state": "unverified", "source_url": "https://apps.apple.com/us/app/xinxii/id645485548", "checked_at": "2026-09-30" }, { "key": "fact-2", - "value": "Current author royalty terms could not be verified from accessible official pages.", + "value": "Unknown. XinXii's official pages wouldn't load.", "state": "unverified", "source_url": "https://www.xinxii.com/", "checked_at": "2026-09-30" }, { "key": "fact-3", - "value": "Current per-copy deductions could not be verified from accessible official pages.", + "value": "Unknown.", "state": "unverified", "source_url": "https://www.xinxii.com/", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "Current distribution fees could not be verified from accessible official pages.", + "value": "Unknown.", "state": "unverified", "source_url": "https://www.xinxii.com/", "checked_at": "2026-09-30" }, { "key": "fact-5", - "value": "Current exclusivity, payout, and rights terms could not be verified from accessible official pages.", + "value": "Unknown.", "state": "unverified", "source_url": "https://www.xinxii.com/", "checked_at": "2026-09-30" @@ -32736,18 +32736,18 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current official pages were inaccessible; an Apple developer listing dated 2021 is not current terms.", + "comparison_basis": "XinXii's official pages wouldn't load. A 2021 Apple developer listing isn't current terms.", "fee_percent": null, "fee_basis": null, "sort_order": 12, "card": { "owner": "XinXii", "tag": "Ebooks and audiobooks", - "note": "XinXii’s official pages could not be reached, so current terms are unknown.", + "note": "XinXii's official pages wouldn't load, so current terms are unknown.", "take": { - "value": "Royalty unconfirmed", + "value": "Royalty unknown", "percent": null, - "label": "Official terms inaccessible", + "label": "Official site unreachable", "fact_key": "fact-2" }, "lines": [ @@ -32758,7 +32758,7 @@ }, { "label": "FAQ", - "value": "Could not load", + "value": "Wouldn't load", "fact_key": "fact-2" }, { @@ -33846,7 +33846,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The current US pricing page lists Subscription at $99/year and Lifetime Access at $299, both with 85% of net revenue. Official legal/help material also describes a revenue-share route without fixed costs and charges EUR 0.20 per download of a free audiobook under the 2025 terms update. These are different options; confirm the selected plan and retailer rate in the StreetLib account. Retail audiobook earnings are calculated after store payout and StreetLib charges.", + "comparison_basis": "The current US pricing page lists Subscription at $99/year and Lifetime Access at $299, both with 85% of net revenue. Official legal and help pages also describe a revenue-share route without fixed costs and charge EUR 0.20 per download of a free audiobook under the 2025 terms update. These are different options, and the rate depends on which one you pick and the retailer. Retail audiobook earnings are calculated after store payout and StreetLib charges.", "fee_percent": null, "fee_basis": null, "sort_order": 5, @@ -33931,7 +33931,7 @@ }, { "key": "fact-5", - "value": "Official page conflicts: cost section says $50–$400/PFH ($300–$2,500+ for six hours); its FAQ says $150–$400/PFH ($900–$2,500).", + "value": "Author's Republic's page gives two ranges: its cost section says $50–$400 per finished hour ($300–$2,500+ for six hours); its FAQ says $150–$400 per finished hour ($900–$2,500).", "state": "variable", "source_url": "https://www.authorsrepublic.com/create-audiobooks", "checked_at": "2026-09-30" @@ -34294,7 +34294,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Creators 101 lists viewer-ad eligibility as 40,000+ global monthly views and 1,000+ subscribers, while the January 2026 Terms require over 40,000 views and over 1,000 subscribers; the official pages conflict at the exact boundary. Super Likes require at least 500 likes and age 18+.", + "comparison_basis": "Creators 101 lists viewer-ad eligibility as 40,000+ global monthly views and 1,000+ subscribers. The January 2026 Terms say over 40,000 views and over 1,000 subscribers, so exactly 40,000 or 1,000 may not count. Super Likes need at least 500 likes and age 18+.", "fee_percent": null, "fee_basis": null, "sort_order": 0, @@ -35096,7 +35096,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The 2026 Writer Benefits page says payments are made on the 10th of the following month, with a $200 bank minimum or $100 PayPal minimum. A current localized official payment FAQ says a $100 accumulated threshold and lists bank/Payoneer, so payout thresholds and methods conflict. Confirm the offered account terms.", + "comparison_basis": "The 2026 Writer Benefits page says payments go out on the 10th of the following month, with a $200 bank minimum or $100 PayPal minimum. A localized GoodNovel payment FAQ says $100 total and lists bank and Payoneer. Your account terms decide which applies.", "fee_percent": null, "fee_basis": null, "sort_order": 7, @@ -35278,7 +35278,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Pocket FM’s Sherpa FAQ lists an invitation and WBP contract as the path to writer earnings, but the accessible public FAQ does not give a standard share or fee schedule. The general service terms grant a non-exclusive content license; a separate WBP contract may add rights or payment terms.", + "comparison_basis": "Pocket FM's Sherpa FAQ says writers earn through an invitation and a WBP contract. The public FAQ gives no standard share or fees. The general terms grant a non-exclusive content license; a WBP contract may add rights or payment terms.", "fee_percent": null, "fee_basis": null, "sort_order": 9, @@ -36124,7 +36124,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Course earnings vary by plays, content type, and engagement; a return-rate bonus is possible. Pricing docs conflict: the learner FAQ says courses aren't sold individually, while the teacher edit guide says course pricing is editable. Optional donations are separate.", + "comparison_basis": "Course earnings vary by plays, content type, and engagement; a return-rate bonus is possible. Insight Timer's learner FAQ says courses aren't sold one by one, while its teacher guide says you can edit course pricing. Optional donations are separate.", "fee_percent": null, "fee_basis": null, "sort_order": 5, @@ -36451,28 +36451,28 @@ }, { "key": "fact-2", - "value": "The official 2021 teacher handbook says italki took 15% of completed lessons, excluding trials. Current rate is unconfirmed; the 2025 commission update and Help Center page were inaccessible.", + "value": "italki's 2021 teacher handbook says it took 15% of completed lessons, excluding trials. Its 2025 commission update and Help Center page wouldn't load, so the current rate is unknown.", "state": "variable", "source_url": "https://uploads-ssl.webflow.com/5f6c06ea5780254500e94751/612cad97d81cf83b991de98b_Handbook%20for%20New%20Teachers%20-%20English%20%282021%29.pdf", "checked_at": "2026-09-30" }, { "key": "fact-3", - "value": "The 2021 handbook says trials are 30 minutes and paid; completed-lesson commission did not apply. Current trial terms are unconfirmed.", + "value": "The 2021 handbook says trials are 30 minutes and paid, with no commission on them. Current trial terms are unknown.", "state": "variable", "source_url": "https://uploads-ssl.webflow.com/5f6c06ea5780254500e94751/612cad97d81cf83b991de98b_Handbook%20for%20New%20Teachers%20-%20English%20%282021%29.pdf", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "No current teacher signup fee is listed in the accessible official public sources.", + "value": "No teacher sign-up fee is listed on italki's public pages.", "state": "not_public", "source_url": "https://teach.italki.com/", "checked_at": "2026-09-30" }, { "key": "fact-5", - "value": "The 2021 handbook says teacher profiles need approval after setup. Current openings and approval requirements vary by language and are not confirmed.", + "value": "The 2021 handbook says teacher profiles need approval after setup. Openings and approval rules vary by language; current ones are unknown.", "state": "variable", "source_url": "https://uploads-ssl.webflow.com/5f6c06ea5780254500e94751/612cad97d81cf83b991de98b_Handbook%20for%20New%20Teachers%20-%20English%20%282021%29.pdf", "checked_at": "2026-09-30" @@ -36501,14 +36501,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current teachers set their own prices, but current commission terms are unconfirmed. The last directly readable official handbook (2021) lists 15% on completed lessons, excluding 30-minute paid trials; current payment processing fees/options and the 2025 commission update could not be confirmed.", + "comparison_basis": "Teachers set their own prices; the current commission is unknown. The 2021 handbook, the last one that loaded, lists 15% on completed lessons, excluding 30-minute paid trials. Current processing fees and the 2025 commission update are unknown.", "fee_percent": null, "fee_basis": null, "sort_order": 2, "card": { "owner": "italki", "tag": "Language tutoring", - "note": "The current italki rate could not be confirmed. 15% comes from the 2021 handbook.", + "note": "italki's current rate is unknown. The 15% comes from its 2021 handbook.", "take": { "value": "15%", "percent": null, @@ -36586,7 +36586,7 @@ }, { "key": "fact-5", - "value": "New tutor intake limited; no degree needed", + "value": "Cambly takes a limited number of new tutors; no degree needed", "state": "verified", "source_url": "https://email-proxy.cambly.com/english/tutors", "checked_at": "2026-09-30" @@ -37062,7 +37062,7 @@ "card": { "owner": "Lessonface", "tag": "Lessons and group classes", - "note": "Lessonface’s fee table and terms disagree on the referred group rate.", + "note": "Lessonface's fee table and terms disagree on the referred group rate.", "take": { "value": "5–30%", "percent": null, @@ -37649,7 +37649,7 @@ }, { "key": "fact-5", - "value": "With Kajabi Payments, Kajabi calculates/collects tax; the seller registers and files/remits. Current guide confirms a fee on successful Sales Tax-enabled offers but gives no amount; the Jan 2024 post lists $0.15 per taxable sale.", + "value": "With Kajabi Payments, Kajabi calculates and collects tax; you register, file, and remit. Kajabi's tax guide says there's a fee on successful sales with Sales Tax turned on but gives no amount. A January 2024 post lists $0.15 per taxable sale.", "state": "variable", "source_url": "https://help.kajabi.com/articles/sales/kajabi-payments/kajabi-payments-sales-tax-overview", "checked_at": "2026-09-30" @@ -37670,7 +37670,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "US sellers, USD. Current tax guide confirms a Sales Tax-enabled transaction fee but gives no amount; the 2024 launch post lists $0.15 per taxable sale.", + "comparison_basis": "US sellers, USD. Kajabi's tax guide says there's a fee on sales with Sales Tax turned on but gives no amount; a 2024 launch post lists $0.15 per taxable sale.", "fee_percent": 0, "fee_basis": "No revenue share; US third-party provider fees may apply on supported regions and plans.", "sort_order": 3, @@ -39596,14 +39596,14 @@ }, { "key": "fact-2", - "value": "Direct sale: Basic 4.9%, Intermediate 6.9%, Unity 4.59% above R$1M/year; fixed fee R$2.49. Affiliate 8.9% + R$2.49; international 10.9% + R$3.99. The 4.9% commission is waived per item with original price up to R$30; R$2.49 per invoice remains. Help and plan pricing cover different plans; confirm selected plan.", + "value": "Direct sale: Basic 4.9%, Intermediate 6.9%, Unity 4.59% above R$1M/year; fixed fee R$2.49. Affiliate 8.9% + R$2.49; international 10.9% + R$3.99. The 4.9% commission is waived per item with original price up to R$30; R$2.49 per invoice remains. The help page and pricing page cover different plans.", "state": "verified", "source_url": "https://ajuda.eduzz.com/hc/pt-br/articles/1260807397109-Quais-s%C3%A3o-as-taxas-cobradas-pela-Eduzz", "checked_at": "2026-09-30" }, { "key": "fact-3", - "value": "Official pricing does not list a separate processor rate. Official pages conflict on bank transfer charges: R$1.99 on pricing and R$9 in Help.", + "value": "Eduzz's pricing doesn't list a separate processor rate. Its pricing table says bank transfers cost R$1.99; its help page says R$9.", "state": "variable", "source_url": "https://ajuda.eduzz.com/hc/pt-br/articles/1260807406729-Como-solicitar-transfer%C3%AAncia-banc%C3%A1ria", "checked_at": "2026-09-30" @@ -39646,7 +39646,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Brazilian seller, BRL. Sale rates depend on plan, channel and ticket. Official pages conflict on transfer fee (R$1.99 pricing table vs R$9 Help article).", + "comparison_basis": "Brazilian seller, BRL. Sale rates depend on plan, channel, and price. Eduzz's pricing table says the transfer fee is R$1.99; its help article says R$9.", "fee_percent": null, "fee_basis": null, "sort_order": 20, @@ -40565,7 +40565,7 @@ "facts": [ { "key": "fact-1", - "value": "Official 2026 comparison lists Grow at ₹29,999/year and Enterprise at ₹39,999 for two years; confirm final feature quote.", + "value": "Classplus's 2026 comparison lists Grow at ₹29,999/year and Enterprise at ₹39,999 for two years. The final quote depends on the features you pick.", "state": "verified", "source_url": "https://classplusapp.com/growth/classplus-vs-online-teaching-platforms-india/", "checked_at": "2026-09-30" @@ -41075,7 +41075,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Royalties are paid in the listing currency. The current author page confirms monthly rolling 12-month tiers; fee amounts come from the official FAQ last updated in 2024.", + "comparison_basis": "Royalties are paid in the listing currency. The current author page shows monthly rolling 12-month tiers; fee amounts come from the official FAQ last updated in 2024.", "fee_percent": null, "fee_basis": null, "sort_order": 1, @@ -42438,7 +42438,7 @@ }, { "key": "fact-2", - "value": "Official US page prints “0.99¢” per ticket for both options; the USD fixed-fee amount is unclear.", + "value": "TicketSource's US page lists the fixed fee as \"0.99¢\" per ticket for both options, so it's not clear if that means 99 cents or less than a cent.", "state": "variable", "source_url": "https://www.ticketsource.com/us/pricing", "checked_at": "2026-09-30" @@ -42491,7 +42491,7 @@ "card": { "owner": "TicketSource", "tag": "Event tickets", - "note": "The US page prints the fixed fee as “0.99¢”, so the real amount is unclear.", + "note": "The US page prints the fixed fee as \"0.99¢\", which could mean 99 cents or less than a cent.", "take": { "value": "3.5%", "percent": null, @@ -42877,7 +42877,7 @@ }, { "key": "fact-4", - "value": "Current exclusivity terms are not accessible in public seller help", + "value": "Ravelry's public seller help doesn't list exclusivity terms", "state": "not_public", "source_url": "https://www.ravelry.com/help/search?query=selling+patterns", "checked_at": "2026-09-30" @@ -43048,7 +43048,7 @@ }, { "key": "fact-2", - "value": "The official chart lists 4% + $0.35 per USD sale and a 5% seller fee in the monthly sales band, but says these rates apply from June 1, 2022. Current rates are unconfirmed.", + "value": "LoveCrafts' fee chart lists 4% + $0.35 per USD sale and a 5% seller fee in the monthly sales band, with rates dated from June 1, 2022.", "state": "variable", "source_url": "https://www.lovecrafts.com/en-us/pages/designer-handbook-payment-pricing-taxes", "checked_at": "2026-09-30" @@ -43091,14 +43091,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "US seller, USD. The linked fee chart says its rates apply from June 1, 2022; 2026 handbook text does not confirm they remain current. GBP/EUR thresholds also differ.", + "comparison_basis": "US seller, USD. The linked fee chart says its rates apply from June 1, 2022; the 2026 handbook doesn't repeat them. GBP and EUR thresholds differ.", "fee_percent": null, "fee_basis": null, "sort_order": 2, "card": { "owner": "LoveCrafts", "tag": "Knitting and crochet patterns", - "note": "LoveCrafts’ fee chart is dated June 2022, so current rates are not confirmed.", + "note": "LoveCrafts' fee chart is dated June 2022.", "take": { "value": "4%", "percent": null, @@ -43162,28 +43162,28 @@ "facts": [ { "key": "fact-1", - "value": "Current seller signup and listing costs could not be verified because the official marketplace was inaccessible.", + "value": "Unknown. Craftsuprint's site blocked access when we checked.", "state": "unverified", "source_url": "https://www.craftsuprint.com/", "checked_at": "2026-09-30" }, { "key": "fact-2", - "value": "Current seller share and royalty terms could not be verified because the official marketplace was inaccessible.", + "value": "Unknown.", "state": "unverified", "source_url": "https://www.craftsuprint.com/", "checked_at": "2026-09-30" }, { "key": "fact-3", - "value": "Current seller payment-processing terms could not be verified because the official marketplace was inaccessible.", + "value": "Unknown.", "state": "unverified", "source_url": "https://www.craftsuprint.com/", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "Current exclusivity terms could not be verified because the official marketplace was inaccessible.", + "value": "Unknown.", "state": "unverified", "source_url": "https://www.craftsuprint.com/", "checked_at": "2026-09-30" @@ -43200,18 +43200,18 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The official marketplace returned 403 to public retrieval; current seller fees and terms remain unavailable in accessible official pages.", + "comparison_basis": "Craftsuprint's site blocked public access (error 403), so its seller fees and terms are unknown.", "fee_percent": null, "fee_basis": null, "sort_order": 3, "card": { "owner": "Craftsuprint", "tag": "Card kits and craft designs", - "note": "Craftsuprint’s site blocked access, so its terms could not be checked.", + "note": "Craftsuprint's site blocked access, so its terms are unknown.", "take": { - "value": "Share unconfirmed", + "value": "Share unknown", "percent": null, - "label": "Official terms inaccessible", + "label": "Site blocked access", "fact_key": "fact-2" }, "lines": [ @@ -43258,14 +43258,14 @@ }, { "key": "fact-3", - "value": "Aug 2023 partner terms say processing is deducted before the membership pool; current mechanics are not reconfirmed.", + "value": "From Design Bundles' August 2023 partner terms: processing is deducted before the membership pool.", "state": "variable", "source_url": "https://designbundles.net/designer-termsconditions", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "Aug 2023 partner terms grant a non-exclusive licence; current seller terms are not reconfirmed.", + "value": "From the August 2023 partner terms: a non-exclusive licence.", "state": "variable", "source_url": "https://designbundles.net/designer-termsconditions", "checked_at": "2026-09-30" @@ -44432,7 +44432,7 @@ "facts": [ { "key": "fact-1", - "value": "Official pricing lists Starter at $399/month billed yearly ($4,788/year) or $499 monthly. Its 2026 comparison page lists $149/month billed yearly or $199 monthly. Growth pricing matches at $1,500/$1,750; current Starter price is unclear.", + "value": "Bettermode's pricing page lists Starter at $399/month billed yearly ($4,788/year) or $499 monthly. Its 2026 comparison page lists Starter at $149/month billed yearly or $199 monthly. Both list Growth at $1,500/$1,750.", "state": "variable", "source_url": "https://home.bettermode.com/pricing", "checked_at": "2026-09-30" @@ -45507,7 +45507,7 @@ "facts": [ { "key": "fact-1", - "value": "Premium is $29/month; official guides conflict on whether billing is per community or per account.", + "value": "Premium is $29/month. LaunchPass guides disagree on whether that's per community or per account.", "state": "variable", "source_url": "https://help.launchpass.com/en/articles/1745573-step-3-select-your-launchpass-plan-complete-pricing-guide", "checked_at": "2026-09-30" @@ -45556,7 +45556,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Premium fee is 3.5%; official guides conflict on whether the $29 monthly plan fee is per community or per account. Former Premium users on Free may pay 12.5%.", + "comparison_basis": "Premium fee is 3.5%. LaunchPass guides disagree on whether the $29 monthly plan fee is per community or per account. Former Premium users on Free may pay 12.5%.", "fee_percent": 3.5, "fee_basis": "LaunchPass fee per paid transaction; Stripe processing is additional", "sort_order": 16, @@ -46208,7 +46208,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "US sellers, USD. The current Terms list 3–7% + $0.30–$0.40 processing and possible 10% flat above $30; the pricing page lists 3–10% + $0.30–$0.40. The official pages conflict.", + "comparison_basis": "US sellers, USD. SubscribeStar's terms list 3–7% + $0.30–$0.40 processing, and possibly a flat 10% above $30. Its pricing page lists 3–10% + $0.30–$0.40.", "fee_percent": 5, "fee_basis": "Regular creator platform fee; payment processing and payout costs vary", "sort_order": 22, @@ -47226,14 +47226,14 @@ "facts": [ { "key": "fact-1", - "value": "Jan 2025 Terms list no separate creator plan price; current pricing is unconfirmed.", + "value": "No separate creator plan price, per OnlyFans' January 2025 terms. The current terms wouldn't load.", "state": "variable", "source_url": "https://archive.ph/6mux7", "checked_at": "2026-09-30" }, { "key": "fact-2", - "value": "Jan 2025 Terms charge 20% of fan payments; current rate is unconfirmed.", + "value": "20% of fan payments, per OnlyFans' January 2025 terms.", "state": "variable", "source_url": "https://archive.ph/6mux7", "checked_at": "2026-09-30" @@ -47254,7 +47254,7 @@ }, { "key": "fact-5", - "value": "Jan 2025 Terms require 18+, valid ID and two photos for creators; current rules are unconfirmed.", + "value": "Creators must be 18+ with valid ID and two photos, per the January 2025 terms.", "state": "variable", "source_url": "https://archive.ph/6mux7", "checked_at": "2026-09-30" @@ -47271,14 +47271,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The Jan 13, 2025 capture of OnlyFans Terms sets a 20% fee on fan payments. It lists no separate creator plan price, requires a minimum balance for withdrawal without stating the amount or schedule, and says bank or e-wallet charges can vary. It requires creators to be 18+ and submit ID and two photos. Current Terms were inaccessible, so current conditions remain unconfirmed.", + "comparison_basis": "OnlyFans' terms from Jan 13, 2025 set a 20% fee on fan payments. They list no separate creator plan price, require a minimum balance to withdraw without giving the amount or schedule, and say bank or e-wallet charges vary. Creators must be 18+ and submit ID and two photos. The current terms wouldn't load.", "fee_percent": null, "fee_basis": null, "sort_order": 0, "card": { "owner": "OnlyFans", "tag": "Fan subscriptions", - "note": "The current OnlyFans terms could not be read. The 20% comes from January 2025.", + "note": "The current OnlyFans terms wouldn't load. The 20% comes from January 2025.", "take": { "value": "20%", "percent": null, @@ -47444,7 +47444,7 @@ }, { "key": "fact-3", - "value": "Creator-side processing rate is not published; 2024 fan Payment Terms list a 13% card surcharge, not reconfirmed in 2026 Creator Terms.", + "value": "FanFix doesn't publish a creator processing rate. Its 2024 fan payment terms list a 13% card surcharge; the 2026 creator terms don't mention it.", "state": "not_public", "source_url": "https://auth.fanfix.io/payment-terms-condition", "checked_at": "2026-09-30" @@ -47543,7 +47543,7 @@ }, { "key": "fact-2", - "value": "Standard: 10%; Creators+ Help Center: 20%; Shop: 0%. Shop article says DMs/exclusive content remain 10%, conflicting with Help Center scope.", + "value": "Standard: 10%; Creators+: 20% per the Help Center; Shop: 0%. The Shop article says DMs and exclusive content stay at 10%, which differs from the Help Center.", "state": "variable", "source_url": "https://help.passes.com/article/8cd9e3ec-creators-plan", "checked_at": "2026-09-30" @@ -47597,14 +47597,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Official pages list 10% standard, 20% Creators+, and 0% Shop. The Help Center and Shop article conflict on DMs and exclusive content.", + "comparison_basis": "Passes' pages list 10% standard, 20% Creators+, and 0% Shop. The Help Center and Shop article disagree on DMs and exclusive content.", "fee_percent": null, "fee_basis": null, "sort_order": 3, "card": { "owner": "Passes", "tag": "Memberships and paid content", - "note": "Passes’ pages disagree on which sales the 20% Creators+ rate covers.", + "note": "Passes' pages disagree on which sales the 20% Creators+ rate covers.", "take": { "value": "10%", "percent": null, @@ -48022,28 +48022,28 @@ "facts": [ { "key": "fact-1", - "value": "Current creator signup cost could not be verified because official pages were inaccessible.", + "value": "Unknown. JustForFans' official pages wouldn't load.", "state": "unverified", "source_url": "https://justfor.fans/", "checked_at": "2026-09-30" }, { "key": "fact-2", - "value": "Last-accessible official commission article (Dec 2021): performers received 80% across sales; exclusive-program performers received 85% on selected sales. Current share is unconfirmed.", + "value": "From a December 2021 JustForFans article: performers got 80% across sales, and exclusive-program performers got 85% on selected sales.", "state": "variable", "source_url": "https://fanshelp.freshdesk.com/support/solutions/articles/47001147016-what-percentage-of-sales-do-i-receive-", "checked_at": "2026-09-30" }, { "key": "fact-3", - "value": "Current creator-side processing rate could not be verified from accessible official pages.", + "value": "Unknown.", "state": "unverified", "source_url": "https://justfor.fans/", "checked_at": "2026-09-30" }, { "key": "fact-4", - "value": "Last-accessible official payout article (July 2021): weekly or monthly payouts after a $50 minimum. Current schedule and fees are unconfirmed.", + "value": "From a July 2021 payout article: weekly or monthly payouts after a $50 minimum.", "state": "variable", "source_url": "https://fanshelp.freshdesk.com/support/solutions/articles/47001147024-how-often-do-you-pay-performers-", "checked_at": "2026-09-30" @@ -48075,14 +48075,14 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Official articles last updated in 2021 list 80% standard and 85% on selected exclusive-program sales, plus weekly/monthly payouts above $50. Current rates and schedule are unconfirmed.", + "comparison_basis": "JustForFans articles last updated in 2021 list 80% standard and 85% on selected exclusive-program sales, plus weekly or monthly payouts above $50. Its current pages wouldn't load.", "fee_percent": null, "fee_basis": null, "sort_order": 7, "card": { "owner": "JustForFans", "tag": "Fan subscriptions", - "note": "JustForFans’ current pages could not be reached. These rates are from 2021.", + "note": "JustForFans' current pages wouldn't load. These rates are from 2021.", "take": { "value": "20%", "percent": null, @@ -48092,7 +48092,7 @@ "lines": [ { "label": "Site", - "value": "Could not load", + "value": "Wouldn't load", "fact_key": "fact-1" }, { @@ -48249,7 +48249,7 @@ "facts": [ { "key": "fact-1", - "value": "Account is free; registration guide says 5 media for certification, while the separate certification page says 3.", + "value": "Free account. MYM's registration guide says certification needs 5 media items; its certification page says 3.", "state": "variable", "source_url": "https://support.mym.fans/hc/en-us/articles/4410309344018-Registration", "checked_at": "2026-09-30" @@ -48277,7 +48277,7 @@ }, { "key": "fact-5", - "value": "Creators must be 18+ and provide ID, selfie and contact details; official guides conflict on certification media (3 vs 5).", + "value": "Creators must be 18+ and give ID, a selfie, and contact details.", "state": "variable", "source_url": "https://support.mym.fans/hc/en-us/articles/360014054079-Certification", "checked_at": "2026-09-30" @@ -48310,7 +48310,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Standard creator fees apply after a 7% transaction charge; official certification guides conflict on the required media count.", + "comparison_basis": "Standard creator fees apply after a 7% transaction charge. MYM's certification guides disagree on how many media items are needed.", "fee_percent": null, "fee_basis": null, "sort_order": 9, @@ -48361,7 +48361,7 @@ "value": "20%", "percent": 20, "label": "Private media and tips", - "note": "Also shows", + "note": "Also covers shows", "fact_key": "fact-2" } ] @@ -49100,7 +49100,7 @@ }, { "key": "fact-2", - "value": "At 1,000 members, Publisher is $29/month billed annually. The comparison table lists Starter/Publisher at 1,000, Business at 10,000, Custom unlimited; the overview conflicts on Business.", + "value": "At 1,000 members, Publisher is $29/month billed yearly. The comparison table lists Starter and Publisher at 1,000, Business at 10,000, and Custom unlimited; the overview page gives a different figure for Business.", "state": "variable", "source_url": "https://ghost.org/pricing", "checked_at": "2026-09-30" @@ -49603,7 +49603,7 @@ "facts": [ { "key": "fact-1", - "value": "Current product docs describe a site, newsletter and subscriber list, not paid memberships. Terms allow publisher transactions, but current paid-feature availability is unclear.", + "value": "Paragraph's product docs describe a site, newsletter, and subscriber list, not paid memberships. Its terms allow publisher transactions, but it's not clear if paid features are available now.", "state": "variable", "source_url": "https://paragraph.com/terms-of-use", "checked_at": "2026-09-30" @@ -49664,7 +49664,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current product docs describe publishing and newsletters; terms permit publisher transactions, but current paid-membership availability, fees and processor are not confirmed.", + "comparison_basis": "Paragraph's product docs describe publishing and newsletters. Its terms allow publisher transactions, but they don't describe paid memberships, fees, or a processor.", "fee_percent": null, "fee_basis": null, "sort_order": 7, @@ -49691,7 +49691,7 @@ }, { "label": "Paid features", - "value": "Unclear", + "value": "Not clear", "fact_key": "fact-1" } ], @@ -50353,7 +50353,7 @@ }, { "key": "fact-5", - "value": "July 2026 payout guide conflicts: cadence section says verified streamers may choose weekly/monthly, others monthly; FAQ says monthly except local weekly/daily rules. Country thresholds vary.", + "value": "Kick's July 2026 payout guide says two things: one section says verified streamers can pick weekly or monthly payouts and others get monthly; its FAQ says monthly, except where local rules allow weekly or daily. Thresholds vary by country.", "state": "variable", "source_url": "https://help.kick.com/en/articles/15159725-kick-payout-schedule-thresholds-and-methods", "checked_at": "2026-09-30" @@ -50370,7 +50370,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Subscription price is $5 USD. The July 2026 payout guide conflicts on weekly versus monthly cadence; country thresholds and local rules vary.", + "comparison_basis": "Subscription price is $5 USD. Kick's July 2026 payout guide disagrees with itself on weekly versus monthly payouts. Thresholds and local rules vary by country.", "fee_percent": null, "fee_basis": null, "sort_order": 2, @@ -50771,7 +50771,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Subscription rates above $50K conflict across English and Spanish official help (90% vs 80%). Subscription payout timing also conflicts: Spanish says about 60 days after month-end, English says up to 90 days. Original Content Rewards is separate from the Revenue Sharing program that ended Sep 7, 2026.", + "comparison_basis": "X's English and Spanish help pages disagree on subscription rates above $50K: 90% versus 80%. They also disagree on payout timing: about 60 days after month-end in Spanish, up to 90 days in English. Original Content Rewards is separate from the Revenue Sharing program that ended Sep 7, 2026.", "fee_percent": null, "fee_basis": null, "sort_order": 6, @@ -51702,7 +51702,7 @@ }, { "key": "fact-2", - "value": "Current Help Center does not publish the Series fee rate; 2022 Spanish terms list historical fee categories.", + "value": "TikTok's Help Center doesn't list the Series fee rate. Its 2022 Spanish terms list older fee categories.", "state": "not_public", "source_url": "https://support.tiktok.com/en/business-and-creator/tiktok-series/about-tiktok-series", "checked_at": "2026-09-30" @@ -51744,7 +51744,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current Help pages confirm Series eligibility, video limits and payout timing but do not publish the creator fee schedule. The 2022 Spanish terms list historical fee categories and are not used as current rates.", + "comparison_basis": "TikTok's Help pages cover Series eligibility, video limits, and payout timing but don't list creator fees. The 2022 Spanish terms list older fee categories, which aren't used here as current rates.", "fee_percent": null, "fee_basis": null, "sort_order": 15, @@ -52532,7 +52532,7 @@ "id": "selling-video-subscriptions-rentals-and-downloads--gudsho", "section_id": "selling-video-subscriptions-rentals-and-downloads", "name": "GUDSHO", - "summary": "Video hosting and marketing service; current paid VOD monetization is unconfirmed.", + "summary": "Video hosting and marketing service. It's not clear if it sells paid video directly.", "website_url": "https://www.gudsho.com/", "icon_path": "/platform-fees/icons/www-gudsho-com.ico", "facts": [ @@ -52599,7 +52599,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current official pages do not confirm native paid-VOD checkout or publish a transaction-fee schedule. GudSho promotes embedding exclusive videos on membership sites; the official VOD blog is general education, not a product or fee specification.", + "comparison_basis": "GudSho's pages don't describe a built-in paid video checkout or list transaction fees. GudSho promotes embedding exclusive videos on membership sites; its VOD blog is general advice, not a product or fee page.", "fee_percent": null, "fee_basis": null, "sort_order": 6, @@ -53103,7 +53103,7 @@ }, { "key": "fact-4", - "value": "Acast+ terms dated Oct 2022 list a 15% admin fee plus Stripe processing; current applicability is unconfirmed.", + "value": "From Acast+ terms dated October 2022: a 15% admin fee plus Stripe processing.", "state": "variable", "source_url": "https://www.acast.com/terms-and-conditions-acast-plus", "checked_at": "2026-09-30" @@ -53139,7 +53139,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "US creator plans, USD. Standard creator terms give Acast 50% of net ad revenue. Advanced deals use separate term sheets; current Advanced terms may add a variable CPM for HLS. Acast+ terms dated October 2022 list a 15% admin fee plus Stripe processing; current applicability is unconfirmed.", + "comparison_basis": "US creator plans, USD. Standard creator terms give Acast 50% of net ad revenue. Advanced deals use separate term sheets; current Advanced terms may add a variable CPM for HLS. Acast+ terms dated October 2022 list a 15% admin fee plus Stripe processing.", "fee_percent": null, "fee_basis": null, "sort_order": 4, @@ -54802,7 +54802,7 @@ }, { "key": "fact-4", - "value": "Terms say Rp5,000 per withdrawal; Help Center says queued withdrawals are free and instant withdrawals cost Rp5,000. Official sources conflict.", + "value": "Trakteer's terms say Rp5,000 per withdrawal. Its Help Center says queued withdrawals are free and instant ones cost Rp5,000.", "state": "variable", "source_url": "https://new.trakteer.id/terms", "checked_at": "2026-09-30" @@ -56171,7 +56171,7 @@ }, { "key": "fact-2", - "value": "The recently updated mentor FAQ says there is no commission; the Terms say GrowthMentor currently retains 15%. The official pages conflict.", + "value": "GrowthMentor's recently updated mentor FAQ says there's no commission. Its Terms say it currently keeps 15%.", "state": "variable", "source_url": "https://www.growthmentor.com/faq/how-do-mentors-get-paid", "checked_at": "2026-09-30" @@ -56202,7 +56202,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Mentor payouts; the current FAQ and Terms state conflicting commission rates.", + "comparison_basis": "Mentor payouts. GrowthMentor's FAQ and Terms give different commission rates.", "fee_percent": null, "fee_basis": null, "sort_order": 4, @@ -58087,7 +58087,7 @@ "facts": [ { "key": "fact-1", - "value": "Published table lists 20% on $0–$300, 10% on $301–$3,000, and 5% from $3,000 per client. Prose says 5% applies above $3,000, leaving that exact boundary ambiguous.", + "value": "Workana's table lists 20% on $0–$300, 10% on $301–$3,000, and 5% from $3,000 per client. Its text says 5% applies above $3,000, so it's not clear which rate applies at exactly $3,000.", "state": "verified", "source_url": "https://help.workana.com/hc/en-us/articles/360041235874-How-is-the-commission-calculated-at-Workana", "checked_at": "2026-09-30" @@ -58987,7 +58987,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The official platform update from May 2014 raised the completed-commission fee to 15% and said automatic payouts, when used, ran on the 15th of the month. These are historical figures, not confirmed current terms. Current public seller pages show active commission listings but do not publish a current seller fee, signup cost, payment-provider charge or payout schedule.", + "comparison_basis": "An official update from May 2014 raised the completed-commission fee to 15% and said automatic payouts, when used, ran on the 15th of the month. These are 2014 figures. Current seller pages show active commission listings but don't list a seller fee, sign-up cost, payment charge, or payout schedule.", "fee_percent": null, "fee_basis": null, "sort_order": 13, @@ -59199,7 +59199,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Artistree’s official seller guide covers custom digital products and services delivered through its request forms. Artists can apply separately to the marketplace, where Artistree verifies applicants. Its public seller pages reviewed do not state commission, required plan cost, payment-processing charges or payout timing. Third-party fee claims are excluded.", + "comparison_basis": "Artistree's seller guide covers custom digital products and services delivered through its request forms. Artists can apply separately to the marketplace, where Artistree checks applicants. Its public seller pages don't list commission, plan cost, processing charges, or payout timing. Fee claims from other sites aren't used here.", "fee_percent": null, "fee_basis": null, "sort_order": 15, @@ -61147,7 +61147,7 @@ }, { "key": "fact-3", - "value": "Cash-out eligibility is required, but official sources conflict: the user page says Creator Score >10K plus Green; the developer guide says ≥40K plus Bronze/Silver/Gold and excludes Founder/Free.", + "value": "You must qualify to cash out, but Civitai's pages disagree: the user page says Creator Score over 10K plus Green; the developer guide says 40K or more plus Bronze, Silver, or Gold, and excludes Founder and Free.", "state": "variable", "source_url": "https://civitai.red/creator-program", "checked_at": "2026-09-30" @@ -61182,7 +61182,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Current extraction fees are 0% on the first 100,000 Buzz, then 5%, 10%, and 15% across higher brackets. Official user and developer guides conflict on score and membership eligibility; threshold is unresolved. Payout-partner invitation follows $50 Ready to Withdraw.", + "comparison_basis": "Extraction fees are 0% on the first 100,000 Buzz, then 5%, 10%, and 15% in higher brackets. Civitai's user and developer guides disagree on the score and membership needed. You're invited to the payout partner after $50 is Ready to Withdraw.", "fee_percent": null, "fee_basis": null, "sort_order": 4, @@ -62133,7 +62133,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Amazon says royalty is the product offer price less applicable tax and Amazon costs; the exact product-level schedule is linked from the Developer page but requires account access. The accessible page confirms a free account, Amazon product pages, worldwide shipping and customer service.", + "comparison_basis": "Amazon says royalty is the offer price minus tax and Amazon costs. The product-level schedule needs account access. The public page shows a free account, Amazon product pages, worldwide shipping, and customer service.", "fee_percent": null, "fee_basis": null, "sort_order": 7, @@ -62738,7 +62738,7 @@ }, { "key": "fact-2", - "value": "Premium is $39/month. Annual prices conflict: pricing page $24.99/month billed annually, Help $299/year, Terms $299.88/year; exact annual charge is unresolved.", + "value": "Premium is $39/month. Printify's pages give different yearly prices: the pricing page says $24.99/month billed yearly, Help says $299/year, and the Terms say $299.88/year.", "state": "variable", "source_url": "https://printify.com/pricing/", "checked_at": "2026-09-30" @@ -62796,7 +62796,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "USD. Premium costs $39/month. Official sources conflict on annual billing: the pricing page shows $24.99/month billed annually, Help says $299/year, and the Terms list $299.88/year; the exact annual charge is unresolved. Connected stores pay the seller directly; Printify separately bills production and shipping. Pop-Up Store terms are distinct.", + "comparison_basis": "USD. Premium costs $39/month. Printify's pages give different yearly prices: the pricing page says $24.99/month billed yearly, Help says $299/year, and the Terms say $299.88/year. Connected stores pay the seller directly; Printify separately bills production and shipping. Pop-Up Store terms are distinct.", "fee_percent": null, "fee_basis": null, "sort_order": 13, @@ -63273,7 +63273,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "The official earnings table is effective November 1, 2023 and was last updated January 28, 2024; no newer schedule was confirmed. It lists 50% fine-art print earnings, 30% for most other items, and $5 for frames. Promotions or artist-set print prices can change earnings. Balances clear after 30 days; PayPal transfers may take up to 30 business days after request.", + "comparison_basis": "The official earnings table took effect November 1, 2023 and was last updated January 28, 2024. It lists 50% fine-art print earnings, 30% for most other items, and $5 for frames. Promotions or artist-set print prices can change earnings. Balances clear after 30 days; PayPal transfers may take up to 30 business days after request.", "fee_percent": null, "fee_basis": null, "sort_order": 18, @@ -64725,7 +64725,7 @@ }, { "key": "fact-5", - "value": "IVA treatment is unclear: the current pricing page omits it on standard rates; an 11 May 2026 official article adds it.", + "value": "Mercado Pago's pricing page doesn't add IVA to standard rates; an official article from 11 May 2026 does. So it's not clear if IVA applies on top.", "state": "variable", "source_url": "https://www.mercadopago.com.mx/blog/link-de-cobro-hot-sale", "checked_at": "2026-09-30" @@ -64742,7 +64742,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Mexico, MXN, one-time Payment Links. Cards, wallet and SPEI are 3.49%, 3.19% or 2.95% by release time, plus MXN 4; Oxxo/cash is 3.79% plus MXN 4. The main fee page does not add IVA to those rates, while an official 11 May 2026 article does. Tax treatment is unclear. Installment fees separately show IVA. Some new sellers start at seven-day release; international card pricing is not listed.", + "comparison_basis": "Mexico, MXN, one-time Payment Links. Cards, wallet and SPEI are 3.49%, 3.19% or 2.95% by release time, plus MXN 4; Oxxo/cash is 3.79% plus MXN 4. The main fee page doesn't add IVA to those rates, while an official 11 May 2026 article does. Installment fees show IVA separately. Some new sellers start at seven-day release; international card pricing is not listed.", "fee_percent": null, "fee_basis": null, "sort_order": 10, @@ -64777,7 +64777,7 @@ }, { "label": "IVA", - "value": "Unclear", + "value": "Not clear", "fact_key": "fact-5" } ], @@ -65104,7 +65104,7 @@ }, { "key": "fact-4", - "value": "7-day hold; next-business-day transfer. Official sources conflict: THB20 up to THB2M in pricing/transfer guide; May 27 FAQ says THB30 below THB2M and THB150 above, plus a bank fee.", + "value": "7-day hold; transfers arrive the next business day. Opn's pricing page and transfer guide say THB20 up to THB2M; its May 27 FAQ says THB30 below THB2M and THB150 above, plus a bank fee.", "state": "variable", "source_url": "https://docs.omise.co/how-much-does-omise-cost", "checked_at": "2026-09-30" @@ -65136,7 +65136,7 @@ } ], "checked_at": "2026-09-30", - "comparison_basis": "Thailand, THB. The pricing page says displayed payment rates are subject to 7% VAT: cards/installments 3.65%, PromptPay 1.65%, mobile banking/direct debit THB10. Funds are held seven days; transfers are sent once daily and reach the bank next business day. Pricing and the transfer guide say THB20 up to THB2M and THB150 above; the latest cost FAQ (27 May 2026) says THB30 below THB2M and THB150 above, plus a bank fee over THB2M. Official sources conflict on the lower transfer tier. Other market rates differ.", + "comparison_basis": "Thailand, THB. The pricing page says displayed payment rates are subject to 7% VAT: cards/installments 3.65%, PromptPay 1.65%, mobile banking/direct debit THB10. Funds are held seven days; transfers are sent once daily and reach the bank next business day. Pricing and the transfer guide say THB20 up to THB2M and THB150 above; the latest cost FAQ (27 May 2026) says THB30 below THB2M and THB150 above, plus a bank fee over THB2M. Other market rates differ.", "fee_percent": null, "fee_basis": null, "sort_order": 13, From 3f856c983696447304272208b89a70281be4ea99 Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:21:20 +0530 Subject: [PATCH 5/9] Keep helper containers, image variants, and duplicates out of self-host apps Co-Authored-By: Claude Opus 5.5 --- scripts/self-host-apps/lib/classification.mjs | 51 ++++++++++++ scripts/self-host-apps/lib/schema.mjs | 6 ++ ...01090000_clean_self_host_app_artifacts.sql | 78 +++++++++++++++++++ ...1500_unpublish_docker_in_docker_helper.sql | 15 ++++ ...0_merge_exact_self_host_app_duplicates.sql | 53 +++++++++++++ tests/self-host-apps/pipeline.test.mjs | 26 ++++++- 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 scripts/self-host-apps/lib/classification.mjs create mode 100644 supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql create mode 100644 supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql create mode 100644 supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql diff --git a/scripts/self-host-apps/lib/classification.mjs b/scripts/self-host-apps/lib/classification.mjs new file mode 100644 index 00000000..43026afa --- /dev/null +++ b/scripts/self-host-apps/lib/classification.mjs @@ -0,0 +1,51 @@ +const dependencyImagePattern = /^(?:docker\.io\/)?(?:library\/)?(?:redis|valkey|postgres|postgresql|mariadb|mysql)(?::|$)/i; +const dependencyNamePattern = /(?:^|[- ])(?:redis|valkey|postgres|postgresql|mariadb|mysql)(?:[- ]|$)/i; +const standaloneServiceSlugs = new Set([ + "redis", "valkey", "postgres", "postgresql", "mariadb", "mysql", +]); +const duplicateAliasSlugs = new Set([ + "elastic-search", "actualserver", "apache-solr", "baby-buddy", "flowise", + "lyrionmusicserver", "nginx-proxy-manager", "nzbhydra2", +]); + +/** Classify only records with direct evidence that they are not standalone apps. */ +export function classifySelfHostRecord(record = {}) { + const slug = String(record.slug ?? "").toLowerCase(); + const name = String(record.name ?? "").toLowerCase(); + const summary = String(record.summary ?? "").toLowerCase(); + const image = String(record.docker_image ?? "").trim(); + const identity = `${slug} ${name}`; + + if (duplicateAliasSlugs.has(slug)) { + return { kind: "duplicate", reason: "reviewed duplicate slug for an existing app record" }; + } + + if (/\b(?:example|demo|sample) stack\b|\bexample deployment\b|\bdemo deployment\b/.test(summary)) { + return { kind: "example_stack", reason: "summary identifies an example or demo stack" }; + } + + if (/\b(?:dependency|dependancy) container\b|\b(?:postgresql|redis|valkey|mysql|mariadb) companion for\b|\bexternal database for\b|\bdatabase for the .{1,60} suite\b|\b(?:redis|valkey) persistence backend\b|\b(?:database|cache|redis-compatible valkey service) .{0,50} for (?:immich|makerworks|onlyoffice)\b|\bused by (?:immich|lucia|makerworks|tube ?archivist) for\b/.test(summary)) { + return { kind: "dependency_container", reason: "source summary identifies a helper service for another app" }; + } + + if (/\bseparate docker daemon inside a container\b|\bbuild tools can create images\b/.test(summary)) { + return { kind: "dependency_container", reason: "container runtime helper for other build tools" }; + } + + if (!standaloneServiceSlugs.has(slug) && dependencyNamePattern.test(identity) && dependencyImagePattern.test(image) && + !/\b(?:database|cache|queue|broker) (?:management|admin|monitor|insight|browser|ui|web interface)\b/.test(summary)) { + return { kind: "dependency_container", reason: "app-branded name uses a generic database or cache image" }; + } + + if (/\b(?:official|offical)[ -]docker\b|\b(?:couchdb|elasticsearch|postgres(?:ql)?)[- ]?(?:v)?\d+(?:[.-]x)?\b/i.test(`${slug} ${name}`)) { + return { kind: "image_variant", reason: "record names an upstream image or version rather than a distinct product" }; + } + + return { kind: "application", reason: "no direct artifact evidence matched" }; +} + +export function selfHostRecordClassificationError(record) { + const classification = classifySelfHostRecord(record); + if (classification.kind === "application") return null; + return `${classification.kind} cannot be published as a standalone app: ${classification.reason}`; +} diff --git a/scripts/self-host-apps/lib/schema.mjs b/scripts/self-host-apps/lib/schema.mjs index 68f91132..10e44a2f 100644 --- a/scripts/self-host-apps/lib/schema.mjs +++ b/scripts/self-host-apps/lib/schema.mjs @@ -1,5 +1,6 @@ import { readFile } from "node:fs/promises"; import { selfHostAppCategorySlugs } from "./categories.mjs"; +import { selfHostRecordClassificationError } from "./classification.mjs"; import { slugify, validateSummary } from "./summary-rules.mjs"; export const enumValues = { @@ -323,6 +324,11 @@ export function validateRecord(input, { draft = false } = {}) { const errors = []; const warnings = []; + if (!draft && record.published) { + const classificationError = selfHostRecordClassificationError(record); + if (classificationError) errors.push(classificationError); + } + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(record.slug)) errors.push("slug must be lowercase URL-safe text"); if (!record.name) errors.push("name is required"); if (!draft && record.categories.length === 0) errors.push("at least one category is required"); diff --git a/supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql b/supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql new file mode 100644 index 00000000..80571dd2 --- /dev/null +++ b/supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql @@ -0,0 +1,78 @@ +begin; + +create temporary table self_host_app_cleanup on commit drop as +select slug, canonical_slug, classification +from (values + ('immich-redis', null, 'dependency_container'), + ('immich-postgres', null, 'dependency_container'), + ('lucia-postgresql', null, 'dependency_container'), + ('makerworks-postgres', null, 'dependency_container'), + ('spamfilter-redis', null, 'dependency_container'), + ('tubearchivist-redis', null, 'dependency_container'), + ('onlyoffice-mysql-server', null, 'dependency_container'), + ('couchdb-offical-docker-v1-x', 'couchdb', 'image_variant'), + ('couchdb-v2-x', 'couchdb', 'image_variant'), + ('elastic-search', 'elasticsearch', 'duplicate'), + ('elasticsearch7', 'elasticsearch', 'image_variant'), + ('elasticsearch8', 'elasticsearch', 'image_variant'), + ('postgresql18', 'postgres', 'image_variant') +) as records(slug, canonical_slug, classification); + +create temporary table self_host_app_cleanup_count on commit drop as +select count(*)::integer as published_before from public.self_host_apps +where published and self_hosting_status='self_hostable'; + +do $$ +begin + if (select count(*) from self_host_app_cleanup) <> 13 then + raise exception 'Expected 13 reviewed records in cleanup manifest'; + end if; + if (select count(*) from public.self_host_apps app join self_host_app_cleanup c using (slug) + where app.published and app.self_hosting_status = 'self_hostable') <> 13 then + raise exception 'Cleanup snapshot changed: expected all 13 records to remain published and self-hostable'; + end if; + if (select count(distinct app.id) from self_host_app_cleanup c join public.self_host_apps app on app.slug=c.canonical_slug + where c.canonical_slug is not null and app.published and app.self_hosting_status='self_hostable') <> 3 then + raise exception 'A canonical app record is missing or unpublished'; + end if; +end $$; + +-- Keep source links from every artifact record on its canonical app before unpublishing it. +update public.self_host_apps canonical +set data_source_urls = ( + select array_agg(distinct merged.url order by merged.url) + from unnest( + coalesce(canonical.data_source_urls, '{}') || coalesce(artifacts.urls, '{}') + ) as merged(url) +), updated_at = now() +from ( + select c.canonical_slug, array_agg(distinct item.url) filter (where item.url is not null) as urls + from self_host_app_cleanup c + join public.self_host_apps artifact on artifact.slug=c.slug + cross join lateral unnest(array[artifact.source_code_url, artifact.website_url, artifact.docs_url, + artifact.install_docs_url, artifact.docker_url]) as item(url) + where c.canonical_slug is not null + group by c.canonical_slug +) artifacts +where canonical.slug=artifacts.canonical_slug; + +update public.self_host_apps +set published=false, updated_at=now() +where slug in (select slug from self_host_app_cleanup); + +update public.self_host_apps set name='CouchDB', updated_at=now() where slug='couchdb'; +update public.self_host_apps set name='Elasticsearch', updated_at=now() where slug='elasticsearch'; +update public.self_host_apps set name='PostgreSQL', updated_at=now() where slug='postgres'; + +do $$ +begin + if (select count(*) from public.self_host_apps where slug in (select slug from self_host_app_cleanup) and not published) <> 13 then + raise exception 'Cleanup did not unpublish all reviewed artifacts'; + end if; + if (select count(*) from public.self_host_apps where published and self_hosting_status='self_hostable') <> + (select published_before - 13 from self_host_app_cleanup_count) then + raise exception 'Unexpected cleaned published count; expected a reduction of exactly 13'; + end if; +end $$; + +commit; diff --git a/supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql b/supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql new file mode 100644 index 00000000..a79985f5 --- /dev/null +++ b/supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql @@ -0,0 +1,15 @@ +begin; + +do $$ +begin + if (select count(*) from public.self_host_apps + where slug='docker-in-docker-dind' and published and self_hosting_status='self_hostable') <> 1 then + raise exception 'Docker-in-Docker cleanup snapshot changed'; + end if; +end $$; + +update public.self_host_apps +set published=false, updated_at=now() +where slug='docker-in-docker-dind'; + +commit; diff --git a/supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql b/supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql new file mode 100644 index 00000000..d381cbba --- /dev/null +++ b/supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql @@ -0,0 +1,53 @@ +begin; + +create temporary table self_host_app_duplicate_cleanup on commit drop as +select slug, canonical_slug +from (values + ('actualserver', 'actual-budget'), + ('apache-solr', 'solr'), + ('baby-buddy', 'babybuddy'), + ('flowise', 'flowise-ai'), + ('lyrionmusicserver', 'lyrion-music-server'), + ('nginx-proxy-manager', 'nginxproxymanager'), + ('nzbhydra2', 'hydra2') +) as records(slug, canonical_slug); + +do $$ +begin + if (select count(*) from self_host_app_duplicate_cleanup) <> 7 then + raise exception 'Expected seven exact duplicate pairs in cleanup manifest'; + end if; + if (select count(*) from public.self_host_apps app join self_host_app_duplicate_cleanup c using (slug) + where app.published and app.self_hosting_status='self_hostable') <> 7 then + raise exception 'Duplicate cleanup snapshot changed'; + end if; + if (select count(distinct app.id) from self_host_app_duplicate_cleanup c + join public.self_host_apps app on app.slug=c.canonical_slug + where app.published and app.self_hosting_status='self_hostable') <> 7 then + raise exception 'A canonical app record is missing or unpublished'; + end if; +end $$; + +update public.self_host_apps canonical +set data_source_urls=( + select array_agg(distinct merged.url order by merged.url) + from unnest(coalesce(canonical.data_source_urls,'{}') || coalesce(duplicate.urls,'{}')) as merged(url) +), updated_at=now() +from ( + select c.canonical_slug, + array_agg(distinct item.url) filter (where item.url is not null) as urls + from self_host_app_duplicate_cleanup c + join public.self_host_apps source on source.slug=c.slug + cross join lateral unnest(array[source.source_code_url,source.website_url,source.docs_url, + source.install_docs_url,source.docker_url]) as item(url) + group by c.canonical_slug +) duplicate +where canonical.slug=duplicate.canonical_slug; + +update public.self_host_apps +set published=false, updated_at=now() +where slug in (select slug from self_host_app_duplicate_cleanup); + +update public.self_host_apps set name='Baby Buddy', updated_at=now() where slug='babybuddy'; + +commit; diff --git a/tests/self-host-apps/pipeline.test.mjs b/tests/self-host-apps/pipeline.test.mjs index a14f8014..09a03fed 100644 --- a/tests/self-host-apps/pipeline.test.mjs +++ b/tests/self-host-apps/pipeline.test.mjs @@ -4,12 +4,13 @@ import path from "node:path"; import test from "node:test"; import ts from "typescript"; import { canonicalUrl } from "../../scripts/self-host-apps/lib/candidates.mjs"; +import { classifySelfHostRecord, selfHostRecordClassificationError } from "../../scripts/self-host-apps/lib/classification.mjs"; import { buildCandidateIndexes, buildAppIndexes, classifyCandidate, } from "../../scripts/self-host-apps/lib/dedupe.mjs"; -import { normalizeRecord } from "../../scripts/self-host-apps/lib/schema.mjs"; +import { normalizeRecord, validateRecord } from "../../scripts/self-host-apps/lib/schema.mjs"; import { itemListEntries, softwareApplication, @@ -111,3 +112,26 @@ test("structured data extracts a SoftwareApplication detail", () => { test("unclassified records default self-hosting status to unknown", () => { assert.equal(normalizeRecord({ name: "Example", summary: "A useful example app for testing the pipeline." }).self_hosting_status, "unknown"); }); + +test("classification blocks dependency containers and image variants with direct evidence", () => { + assert.equal(classifySelfHostRecord({ slug: "immich-redis", name: "immich-redis", summary: "Redis-compatible Valkey service used by Immich for background task queues.", docker_image: "valkey/valkey:9" }).kind, "dependency_container"); + assert.equal(classifySelfHostRecord({ slug: "makerworks-postgres", name: "makerworks-postgres", summary: "PostgreSQL database for the MakerWorks suite.", docker_image: "postgres:15" }).kind, "dependency_container"); + assert.equal(classifySelfHostRecord({ slug: "couchdb-offical-docker-v1-x", name: "CouchDB-Offical-Docker-v1.x" }).kind, "image_variant"); + assert.equal(classifySelfHostRecord({ slug: "couchdb-v2-x", name: "CouchDB-v2.x" }).kind, "image_variant"); + assert.equal(classifySelfHostRecord({ slug: "docker-in-docker-dind", name: "Docker-in-Docker", summary: "Runs a separate Docker daemon inside a container so build tools can create images.", docker_image: "docker:dind" }).kind, "dependency_container"); + assert.equal(classifySelfHostRecord({ slug: "elastic-search", name: "Elastic Search" }).kind, "duplicate"); + assert.equal(classifySelfHostRecord({ slug: "actualserver", name: "Actual Budget" }).kind, "duplicate"); + assert.equal(classifySelfHostRecord({ slug: "sample-deployment", name: "Sample Deployment", summary: "Example stack for a demo deployment." }).kind, "example_stack"); + const dependency = { slug: "lucia-postgresql", name: "Lucia-PostgreSQL", summary: "PostgreSQL companion for Lucia.", docker_image: "postgres:17", published: true }; + assert.match(selfHostRecordClassificationError(dependency), /dependency_container/); + assert.ok(validateRecord(dependency).errors.some((error) => /dependency_container/.test(error))); +}); + +test("classification keeps standalone services and companion apps publishable", () => { + for (const record of [ + { slug: "redis", name: "Redis", summary: "In-memory data store.", docker_image: "redis:8" }, + { slug: "postgres", name: "PostgreSQL", summary: "Relational database server.", docker_image: "postgres:18" }, + { slug: "redisinsight", name: "Redis Insight", summary: "Web interface for monitoring Redis databases.", docker_image: "redis/redisinsight:latest" }, + { slug: "pict-rs", name: "Pict-rs", summary: "Image hosting service used by Lemmy for storing and serving uploaded pictures.", docker_image: "asonix/pictrs:latest" }, + ]) assert.equal(classifySelfHostRecord(record).kind, "application", record.slug); +}); From 1fff85eb764f0e7ae2845dfbb26d6c918778e084 Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:26:00 +0530 Subject: [PATCH 6/9] Regenerate the shortcut catalog after the platform fixes Co-Authored-By: Claude Opus 5.5 --- lib/shortcuts/catalog.generated.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/shortcuts/catalog.generated.ts b/lib/shortcuts/catalog.generated.ts index aa90bf9d..766c454d 100644 --- a/lib/shortcuts/catalog.generated.ts +++ b/lib/shortcuts/catalog.generated.ts @@ -605,8 +605,7 @@ export const shortcutAppCatalog = [ "category": "Communication", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 24 }, @@ -682,7 +681,7 @@ export const shortcutAppCatalog = [ "macos", "linux" ], - "shortcutCount": 28 + "shortcutCount": 29 }, { "slug": "filezilla", @@ -938,8 +937,7 @@ export const shortcutAppCatalog = [ "category": "Spreadsheet", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 110 }, From c8d7ca0f6ced7d11eab9ebb6a174a11adf57842f Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:28:05 +0530 Subject: [PATCH 7/9] Add a previewed, delete-guarded sync for platform fee records Co-Authored-By: Claude Opus 5.5 --- scripts/platform-fees/README.md | 34 +++++++++ scripts/platform-fees/record-diff.mjs | 26 +++++++ scripts/platform-fees/sync-dataset.mjs | 71 +++++++++++++++++++ stack127_docs/directory-pages.md | 2 +- .../pages/platform-fees/dataset-sync.test.mjs | 41 +++++++++++ 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 scripts/platform-fees/README.md create mode 100644 scripts/platform-fees/record-diff.mjs create mode 100644 scripts/platform-fees/sync-dataset.mjs create mode 100644 tests/pages/platform-fees/dataset-sync.test.mjs diff --git a/scripts/platform-fees/README.md b/scripts/platform-fees/README.md new file mode 100644 index 00000000..bc9a5c5c --- /dev/null +++ b/scripts/platform-fees/README.md @@ -0,0 +1,34 @@ +# Platform fee records + +Run from the repository root. `sync-dataset.mjs` validates `data/directory.json` +before connecting. It reports every section and entry as insert, update (with +changed field names), delete, or unchanged. Preview uses a read-only transaction. + +```bash +node --experimental-strip-types scripts/platform-fees/sync-dataset.mjs --environment development +node --experimental-strip-types scripts/platform-fees/sync-dataset.mjs --environment production --confirm stack127-production +node --experimental-strip-types scripts/platform-fees/sync-dataset.mjs --environment production --confirm stack127-production --apply +``` + +Connections follow the shared importer: development reads `.env.local`, production +reads `.env.database`, preferring `DATABASE_POOLER_URL` over `DATABASE_URL`. +Production preview also requires the confirmation flag. No credentials are printed. + +Apply locks both record tables, checks for deletions, and syncs in one transaction. +Any failure rolls back. Missing records cause a refusal before any write unless +`--allow-delete` is also passed. The wording release needs no deletions; stop and +check the preview if it shows any. This command never calls `ensurePlatformFeePages` +or inserts page rows. Existing triggers may update public dates and queue refreshes. + +Changed records use the existing `stack127_touch_directory_records` triggers to +queue `platform_fees` events. The cache worker maps these to +`stack127-platform-fees` in `scripts/stack127/cache/targets.mjs`. Triggers stay enabled; +no separate tag refresh call is needed. Identical records don't queue changes. +In Cloudflare hosting mode, the worker dispatches `stack127-frontend-refresh.yml`; +wait for that run to finish and verify the changed public pages before calling the +release complete. A failed refresh leaves events queued. The import's success only +confirms the database commit. See [caching](../../stack127_docs/caching.md) and +[Cloudflare database edits](../../stack127_docs/cloudflare.md#database-edits-in-cloudflare-mode). + +`sync-development.mjs` remains the development page setup command; it can create +missing page rows. Use the records-only command above for reviewed dataset imports. diff --git a/scripts/platform-fees/record-diff.mjs b/scripts/platform-fees/record-diff.mjs new file mode 100644 index 00000000..906a39d0 --- /dev/null +++ b/scripts/platform-fees/record-diff.mjs @@ -0,0 +1,26 @@ +import { isDeepStrictEqual } from 'node:util'; + +// Compare only import-owned columns, like syncRecords. JSON object key order +// is irrelevant; array order is part of the stored public record. +export function diffPlatformFeeRecords(current, incoming) { + return Object.fromEntries(['sections', 'entries'].map(kind => { + const before = new Map(current[kind].map(row => [row.id, row])); + const desired = new Map(incoming[kind].map(row => [row.id, row])); + const columns = [...new Set(incoming[kind].flatMap(row => Object.keys(row)))].sort(); + const records = [...new Set([...before.keys(), ...desired.keys()])].sort().map(id => { + if (!desired.has(id)) return { id, action: 'delete', fields: [] }; + if (!before.has(id)) return { id, action: 'insert', fields: [] }; + const fields = columns.filter(column => !isDeepStrictEqual(before.get(id)[column] ?? null, desired.get(id)[column] ?? null)); + return { id, action: fields.length ? 'update' : 'unchanged', fields }; + }); + const counts = Object.fromEntries(['insert', 'update', 'delete', 'unchanged'].map(action => + [action, records.filter(row => row.action === action).length])); + return [kind, { counts, records }]; + })); +} + +export function assertNoPlatformFeeDeletes(diff, allowDelete = false) { + const removed = ['sections', 'entries'].flatMap(kind => diff[kind].records + .filter(row => row.action === 'delete').map(row => `${kind}/${row.id}`)); + if (removed.length && !allowDelete) throw new Error(`Refusing deletions without --allow-delete: ${removed.join(', ')}`); +} diff --git a/scripts/platform-fees/sync-dataset.mjs b/scripts/platform-fees/sync-dataset.mjs new file mode 100644 index 00000000..637b6f2f --- /dev/null +++ b/scripts/platform-fees/sync-dataset.mjs @@ -0,0 +1,71 @@ +#!/usr/bin/env node +// Records only: no page creation, publication, or metadata edits. +import { readFile } from 'node:fs/promises'; +import { parseArgs } from 'node:util'; +import { pathToFileURL } from 'node:url'; +import pg from 'pg'; +import { validateDirectory } from '../../lib/platform-fees/model.ts'; +import { resolveDatabaseUrl } from '../stack127/tool-pages/lib.mjs'; +import { syncPlatformFeeRecords } from './sync-records.mjs'; +import { diffPlatformFeeRecords, assertNoPlatformFeeDeletes } from './record-diff.mjs'; + +export function datasetArgs(args = process.argv.slice(2)) { + const { values } = parseArgs({ args, options: { + environment: { type: 'string', default: 'development' }, + apply: { type: 'boolean' }, confirm: { type: 'string' }, + 'allow-delete': { type: 'boolean', default: false }, + } }); + if (!['development', 'production'].includes(values.environment)) throw new Error('Use --environment development or production'); + if (values.environment === 'production' && values.confirm !== 'stack127-production') { + throw new Error('Production requires --confirm stack127-production (including previews)'); + } + return values; +} + +async function readRecords(client) { + // to_jsonb preserves date-only strings and numeric values for comparison. + const sections = await client.query('select to_jsonb(r) as record from public.platform_fee_sections r order by id'); + const entries = await client.query('select to_jsonb(r) as record from public.platform_fee_entries r order by id'); + return { sections: sections.rows.map(row => row.record), entries: entries.rows.map(row => row.record) }; +} + +export async function syncDataset(client, data, options, report = console.log) { + await client.query(options.apply ? 'begin' : 'begin isolation level repeatable read read only'); + try { + await client.query("set local lock_timeout = '10s'"); + // Hold both tables against concurrent writers between the guard and sync. + if (options.apply) await client.query('lock table public.platform_fee_sections, public.platform_fee_entries in share row exclusive mode'); + const diff = diffPlatformFeeRecords(await readRecords(client), data); + report(JSON.stringify({ environment: options.environment, apply: Boolean(options.apply), ...diff }, null, 2)); + if (options.apply) { + assertNoPlatformFeeDeletes(diff, options['allow-delete']); + await syncPlatformFeeRecords(client, data); + } + await client.query(options.apply ? 'commit' : 'rollback'); + return diff; + } catch (error) { + await client.query('rollback').catch(() => {}); + throw error; + } +} + +async function main() { + const options = datasetArgs(); + const data = JSON.parse(await readFile(new URL('./data/directory.json', import.meta.url), 'utf8')); + validateDirectory(data); // Fail before reading credentials or connecting. + const client = new pg.Client({ connectionString: resolveDatabaseUrl(options), + connectionTimeoutMillis: 15000, query_timeout: 120000, + // Enforce read-only from connection startup as well as inside the preview. + ...(!options.apply ? { options: '-c default_transaction_read_only=on' } : {}), + application_name: 'stack127-platform-fees-dataset' }); + try { + await client.connect(); + await syncDataset(client, data, options); + console.log(options.apply ? 'Records committed.' : 'Read-only preview; no records changed.'); + console.log('Changed records queue platform_fees events through existing triggers. The cache worker refreshes stack127-platform-fees; in Cloudflare mode, wait for stack127-frontend-refresh.yml and verify the public pages. See stack127_docs/caching.md and stack127_docs/cloudflare.md.'); + } finally { await client.end(); } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + await main().catch(error => { console.error(error.message); process.exitCode = 1; }); +} diff --git a/stack127_docs/directory-pages.md b/stack127_docs/directory-pages.md index dc303008..53a1e8f9 100644 --- a/stack127_docs/directory-pages.md +++ b/stack127_docs/directory-pages.md @@ -33,7 +33,7 @@ Record sync scripts own the records and the generated route files. They leave ex | Directory | Record script | Page fields | | --- | --- | --- | | Keyboard shortcuts | `scripts/keyboard-shortcuts/sync-pages.mjs` | `--apply` inserts missing child rows, then syncs app records and the hub snapshot. Existing page fields stay as written. | -| Platform fees | `scripts/platform-fees/sync-development.mjs`, `sync-records.mjs`, then `prepare-category-manifest.mjs` | Syncs records and inserts missing page rows. The manifest takes reviewed page fields from development. | +| Platform fees | `scripts/platform-fees/sync-development.mjs`, `sync-records.mjs`, then `prepare-category-manifest.mjs` | Syncs records and inserts missing page rows. The manifest takes reviewed page fields from development. For records only, use [`sync-dataset.mjs`](../scripts/platform-fees/README.md): preview first, then apply with production confirmation; deletions need `--allow-delete`. | The managed-development hardware preview scripts, `scripts/stack127/preview-hardware-directories.mjs` and `scripts/zigbee-devices/preview-local-page.mjs`, insert starting rows only when missing. Existing titles, cards, and search fields stay as written. MDX owns the page words. Normal runs publish drafts; published pages stay unchanged. `--remove` unpublishes without deleting rows. Zigbee removal still requires the original preview metadata. Add `--dry-run` to preview either operation and roll back. The old hardware `--refresh` flag overwrote search fields; both scripts now reject it. Use the single-page editor below for reviewed changes. diff --git a/tests/pages/platform-fees/dataset-sync.test.mjs b/tests/pages/platform-fees/dataset-sync.test.mjs new file mode 100644 index 00000000..9a1ecd33 --- /dev/null +++ b/tests/pages/platform-fees/dataset-sync.test.mjs @@ -0,0 +1,41 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { diffPlatformFeeRecords, assertNoPlatformFeeDeletes } from '../../../scripts/platform-fees/record-diff.mjs'; +import { datasetArgs, syncDataset } from '../../../scripts/platform-fees/sync-dataset.mjs'; + +test('dataset diff reports each action and blocks deletion before any write', async () => { + const current = { + sections: [{ id: 'same', fields: [{ key: 'fee', label: 'Fee' }], row_updated_at: 'ignored' }, { id: 'retired' }], + entries: [{ id: 'edit', summary: 'Old', card: { label: 'Fee', percent: 10 } }, { id: 'retired-entry' }], + }; + const incoming = { + sections: [{ id: 'same', fields: [{ label: 'Fee', key: 'fee' }] }, { id: 'new' }], + entries: [{ id: 'edit', summary: 'New', card: { percent: 10, label: 'Fee' } }], + }; + const diff = diffPlatformFeeRecords(current, incoming); + assert.deepEqual(diff.sections.counts, { insert: 1, update: 0, delete: 1, unchanged: 1 }); + assert.deepEqual(diff.entries.counts, { insert: 0, update: 1, delete: 1, unchanged: 0 }); + assert.deepEqual(diff.entries.records[0], { id: 'edit', action: 'update', fields: ['summary'] }); + assert.throws(() => assertNoPlatformFeeDeletes(diff), /--allow-delete.*sections\/retired.*entries\/retired-entry/); + assert.doesNotThrow(() => assertNoPlatformFeeDeletes(diff, true)); + assert.doesNotThrow(() => assertNoPlatformFeeDeletes(diffPlatformFeeRecords(incoming, incoming))); + + const queries = []; + const client = { async query(sql) { + queries.push(sql); + if (sql.includes('to_jsonb')) return { rows: current[sql.includes('platform_fee_sections') ? 'sections' : 'entries'].map(record => ({ record })) }; + return { rows: [], rowCount: 0 }; + } }; + await assert.rejects(syncDataset(client, incoming, { environment: 'development', apply: true }, () => {}), /--allow-delete/); + assert.equal(queries.at(-1), 'rollback'); + assert.ok(queries.some(sql => sql.startsWith('lock table'))); + assert.ok(!queries.some(sql => /^(insert|update|delete|commit)/i.test(sql))); + queries.length = 0; + await syncDataset(client, incoming, { environment: 'development' }, () => {}); + assert.equal(queries[0], 'begin isolation level repeatable read read only'); + assert.equal(queries.at(-1), 'rollback'); + assert.ok(!queries.some(sql => /^(insert|update|delete|lock)/i.test(sql))); + assert.throws(() => datasetArgs(['--environment', 'production', '--apply']), /--confirm stack127-production/); + assert.throws(() => datasetArgs(['--environment', 'production']), /--confirm stack127-production/); + assert.equal(datasetArgs(['--environment', 'production', '--confirm', 'stack127-production', '--apply']).apply, true); +}); From ef3314bedd961739273160316cfc9a1ebc7c18ae Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:36:35 +0530 Subject: [PATCH 8/9] Move self-host cleanup to a previewed command and close republish gaps Co-Authored-By: Claude Opus 5.5 --- package.json | 1 + .../self-host-apps/apply-artifact-cleanup.mjs | 204 ++++++++++++++++ scripts/self-host-apps/apply-qa-overrides.mjs | 13 +- .../artifact-cleanup-manifest.json | 222 ++++++++++++++++++ scripts/self-host-apps/lib/classification.mjs | 44 +++- scripts/self-host-apps/scripts.json | 5 + scripts/self-host-apps/sync-local-to-prod.mjs | 8 + stack127_docs/self-host-apps-data.md | 25 ++ ...01090000_clean_self_host_app_artifacts.sql | 78 ------ ...1500_unpublish_docker_in_docker_helper.sql | 15 -- ...0_merge_exact_self_host_app_duplicates.sql | 53 ----- tests/self-host-apps/pipeline.test.mjs | 21 +- 12 files changed, 537 insertions(+), 152 deletions(-) create mode 100644 scripts/self-host-apps/apply-artifact-cleanup.mjs create mode 100644 scripts/self-host-apps/artifact-cleanup-manifest.json delete mode 100644 supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql delete mode 100644 supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql delete mode 100644 supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql diff --git a/package.json b/package.json index da7629f6..52922ca5 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "self-host-apps:qa-gate": "node scripts/self-host-apps/qa-gate.mjs", "self-host-apps:icon-integrity": "node scripts/self-host-apps/icon-integrity.mjs", "self-host-apps:apply-qa-overrides": "node scripts/self-host-apps/apply-qa-overrides.mjs", + "self-host-apps:apply-artifact-cleanup": "node scripts/self-host-apps/apply-artifact-cleanup.mjs", "self-host-apps:audit-summary-quality": "node scripts/self-host-apps/audit-summary-quality.mjs", "self-host-apps:sync-prod-to-local": "node scripts/self-host-apps/sync-prod-to-local.mjs", "self-host-apps:sync-local-to-prod": "node scripts/self-host-apps/sync-local-to-prod.mjs", diff --git a/scripts/self-host-apps/apply-artifact-cleanup.mjs b/scripts/self-host-apps/apply-artifact-cleanup.mjs new file mode 100644 index 00000000..1ed99cc1 --- /dev/null +++ b/scripts/self-host-apps/apply-artifact-cleanup.mjs @@ -0,0 +1,204 @@ +#!/usr/bin/env node + +import { readFileSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { withClient } from "./lib/db.mjs"; +import { parseArgs } from "./lib/args.mjs"; + +const manifestPath = new URL("./artifact-cleanup-manifest.json", import.meta.url); +const developmentProjectRef = "yzasonowwlvritqicatj"; + +function readEnvFile(path) { + const source = requireEnvText(path); + return Object.fromEntries(source.split(/\r?\n/).flatMap((line) => { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) return []; + const index = trimmed.indexOf("="); + if (index < 0) return []; + return [[trimmed.slice(0, index).trim(), trimmed.slice(index + 1).trim().replace(/^['"]|['"]$/g, "")]]; + })); +} + +function requireEnvText(path) { + try { + return readFileSync(path, "utf8"); + } catch { + throw new Error(`Could not read ${path}. Run this command from the repository root.`); + } +} + +function databaseConfig(environment) { + const file = environment === "production" ? ".env.database" : ".env.local"; + const env = readEnvFile(file); + const siteUrl = new URL(env.SUPABASE_URL ?? ""); + const projectRef = siteUrl.hostname.endsWith(".supabase.co") + ? siteUrl.hostname.slice(0, -".supabase.co".length) + : ""; + const connectionString = env.DATABASE_POOLER_URL || env.DATABASE_URL; + if (!connectionString || !projectRef) throw new Error(`${file} must contain SUPABASE_URL and a database URL.`); + + const connection = new URL(connectionString); + const username = decodeURIComponent(connection.username); + const isDirect = connection.hostname === `db.${projectRef}.supabase.co`; + const isPooler = connection.hostname.endsWith(".pooler.supabase.com") && username.includes(projectRef); + if (!isDirect && !isPooler) throw new Error(`${file} database URL does not match its Supabase project URL.`); + if (environment === "development" && projectRef !== developmentProjectRef) { + throw new Error("Development cleanup requires the managed development project in .env.local."); + } + if (environment === "production" && projectRef === developmentProjectRef) { + throw new Error("Production cleanup refuses the development project."); + } + return connectionString; +} + +function identityMatches(row, expected) { + if (!row) return false; + return row.slug === expected.slug && row.name === expected.name && row.source_code_url === expected.source_code_url; +} + +function targetSources(row) { + return [row.source_code_url, row.website_url, row.docs_url, row.install_docs_url, row.docker_url] + .filter((value) => typeof value === "string"); +} + +function stateFor(record, targetRows, canonicalRows, manifest) { + const target = targetRows.get(record.slug); + const canonicalSpec = record.canonical_slug + ? manifest.canonical_apps.find((item) => item.slug === record.canonical_slug) + : null; + const canonical = canonicalSpec ? canonicalRows.get(canonicalSpec.slug) : null; + const identityOk = identityMatches(target, record); + const canonicalIdentityOk = !canonicalSpec || ( + canonical && canonical.slug === canonicalSpec.slug && canonicalSpec.expected_names.includes(canonical.name) && + canonical.source_code_url === canonicalSpec.source_code_url + ); + const requiredSources = target ? targetSources(target) : []; + const mergedSourcesOk = !canonicalSpec || requiredSources.every((url) => canonical.data_source_urls?.includes(url)); + const nameOk = !canonicalSpec?.clean_name || canonical?.name === canonicalSpec.clean_name; + const alreadyClean = identityOk && canonicalIdentityOk && !target.published && mergedSourcesOk && nameOk; + return { + slug: record.slug, + kind: record.kind, + current_name: target?.name ?? null, + current_source_code_url: target?.source_code_url ?? null, + current_docker_image: target?.docker_image ?? null, + published: target?.published ?? null, + identity_ok: identityOk, + canonical_slug: record.canonical_slug ?? null, + canonical_identity_ok: canonicalIdentityOk, + canonical_name: canonical?.name ?? null, + canonical_published: canonical?.published ?? null, + merged_sources_ok: mergedSourcesOk, + state: alreadyClean ? "already_clean" : "needs_cleanup", + error: !target ? "record missing" : !identityOk ? "target identity mismatch" : !canonicalIdentityOk ? "canonical identity mismatch" : null, + }; +} + +async function loadRows(client, manifest, lock) { + const targetSlugs = manifest.records.map((record) => record.slug); + const canonicalSlugs = manifest.canonical_apps.map((record) => record.slug); + const targets = await client.query( + "select * from public.self_host_apps where slug = any($1::text[]) order by slug" + (lock ? " for update" : ""), + [targetSlugs], + ); + const canonicals = await client.query( + "select * from public.self_host_apps where slug = any($1::text[]) order by slug" + (lock ? " for update" : ""), + [canonicalSlugs], + ); + return { + targets: new Map(targets.rows.map((row) => [row.slug, row])), + canonicals: new Map(canonicals.rows.map((row) => [row.slug, row])), + }; +} + +function validateIdentities(manifest, targetRows, canonicalRows) { + const states = manifest.records.map((record) => stateFor(record, targetRows, canonicalRows, manifest)); + const failures = states.filter((state) => state.error); + if (failures.length) { + throw new Error(`Cleanup stopped: unexpected or missing row identity: ${failures.map((row) => `${row.slug} (${row.error})`).join(", ")}`); + } + for (const spec of manifest.canonical_apps) { + const row = canonicalRows.get(spec.slug); + if (!row || row.slug !== spec.slug || !spec.expected_names.includes(row.name) || row.source_code_url !== spec.source_code_url) { + throw new Error(`Cleanup stopped: canonical identity mismatch for ${spec.slug}.`); + } + } + return states; +} + +async function applyCleanup(client, manifest, targetRows, canonicalRows) { + const sourcesByCanonical = new Map(); + for (const record of manifest.records) { + if (!record.canonical_slug) continue; + const current = targetRows.get(record.slug); + const sourceSet = sourcesByCanonical.get(record.canonical_slug) ?? new Set(); + for (const url of targetSources(current)) sourceSet.add(url); + sourcesByCanonical.set(record.canonical_slug, sourceSet); + } + + for (const [slug, sources] of sourcesByCanonical) { + const current = canonicalRows.get(slug); + const merged = [...new Set([...(current.data_source_urls ?? []), ...sources])].sort(); + const spec = manifest.canonical_apps.find((item) => item.slug === slug); + const cleanName = spec.clean_name ?? current.name; + await client.query( + `update public.self_host_apps + set data_source_urls=$2::text[], name=$3, updated_at=now() + where slug=$1 and (data_source_urls is distinct from $2::text[] or name is distinct from $3)`, + [slug, merged, cleanName], + ); + } + + await client.query( + "update public.self_host_apps set published=false, updated_at=now() where slug = any($1::text[]) and published=true", + [manifest.records.map((record) => record.slug)], + ); +} + +async function main() { + const args = parseArgs(); + const environment = args.environment; + if (!["development", "production"].includes(environment)) { + throw new Error("Pass --environment development or --environment production."); + } + if (args.apply && environment === "production" && !args["confirm-reviewed-cleanup"]) { + throw new Error("Production apply requires --confirm-reviewed-cleanup."); + } + if (args["confirm-reviewed-cleanup"] && environment !== "production") { + throw new Error("--confirm-reviewed-cleanup is only valid with --environment production."); + } + + const manifest = JSON.parse(await readFile(manifestPath, "utf8")); + const databaseUrl = databaseConfig(environment); + await withClient(async (client) => { + await client.query(args.apply ? "begin isolation level serializable" : "begin read only"); + try { + const { targets, canonicals } = await loadRows(client, manifest, Boolean(args.apply)); + const states = validateIdentities(manifest, targets, canonicals); + console.log(`Artifact cleanup preview (${environment})`); + for (const state of states) console.log(JSON.stringify(state)); + const needed = states.filter((state) => state.state !== "already_clean"); + if (!args.apply) { + await client.query("rollback"); + console.log(`Preview only: ${needed.length} record(s) need cleanup; ${states.length - needed.length} already clean.`); + return; + } + + await applyCleanup(client, manifest, targets, canonicals); + const after = await loadRows(client, manifest, true); + const finalStates = validateIdentities(manifest, after.targets, after.canonicals); + const remaining = finalStates.filter((state) => state.state !== "already_clean"); + if (remaining.length) throw new Error(`Cleanup verification failed for ${remaining.map((state) => state.slug).join(", ")}.`); + await client.query("commit"); + console.log(`Applied cleanup to ${needed.length} record(s); ${states.length - needed.length} were already clean.`); + } catch (error) { + await client.query("rollback").catch(() => undefined); + throw error; + } + }, databaseUrl); +} + +main().catch((error) => { + console.error(error.stack ?? error.message); + process.exit(1); +}); diff --git a/scripts/self-host-apps/apply-qa-overrides.mjs b/scripts/self-host-apps/apply-qa-overrides.mjs index cabd8e55..bdfe439a 100644 --- a/scripts/self-host-apps/apply-qa-overrides.mjs +++ b/scripts/self-host-apps/apply-qa-overrides.mjs @@ -4,7 +4,7 @@ import { readFile } from "node:fs/promises"; import { isDeepStrictEqual } from 'node:util'; import { parseArgs, numberArg } from "./lib/args.mjs"; import { withClient } from "./lib/db.mjs"; -import { appColumns, appColumnTypes } from "./lib/schema.mjs"; +import { appColumns, appColumnTypes, validateRecord } from "./lib/schema.mjs"; import { guardPartialCorrection, guardInstallCorrection, mergeReviewedInstallOptions } from "./lib/reviewed-corrections.mjs"; import { installMethods, installOptionsToRow } from "./lib/streamlined-storage.mjs"; import { changedColumns } from '../stack127/database/sync-records.mjs'; @@ -95,9 +95,18 @@ async function applyRecord(client, record, dryRun) { if (current.rowCount === 0) throw new Error(`Missing app row: ${record.slug}`); const currentRow = current.rows[0]; const appId = currentRow.id; - if (Object.hasOwn(record, "categories")) { + if (Object.hasOwn(record, "categories") || currentRow.published || Boolean(record.published)) { currentRow.categories = (await client.query("select category.slug from public.self_host_app_category_links link join public.self_host_app_categories category on category.id = link.category_id where link.app_id = $1 order by category.slug", [appId])).rows.map(row => row.slug); } + const mergedRecord = { + ...currentRow, + ...record, + categories: Object.hasOwn(record, "categories") ? record.categories : currentRow.categories, + }; + const validation = validateRecord(mergedRecord); + if (validation.record.published && validation.errors.length > 0) { + throw new Error(`${record.slug}: correction cannot leave this app published: ${validation.errors.join("; ")}`); + } guardPartialCorrection(record, currentRow); columns = columns.filter(column=>!isDeepStrictEqual(currentRow[column],record[column])); diff --git a/scripts/self-host-apps/artifact-cleanup-manifest.json b/scripts/self-host-apps/artifact-cleanup-manifest.json new file mode 100644 index 00000000..f70f992a --- /dev/null +++ b/scripts/self-host-apps/artifact-cleanup-manifest.json @@ -0,0 +1,222 @@ +{ + "canonical_apps": [ + { + "slug": "couchdb", + "expected_names": [ + "Couchdb", + "CouchDB" + ], + "source_code_url": "https://github.com/apache/couchdb", + "clean_name": "CouchDB" + }, + { + "slug": "elasticsearch", + "expected_names": [ + "Elasticsearch" + ], + "source_code_url": "https://github.com/elastic/elasticsearch", + "clean_name": "Elasticsearch" + }, + { + "slug": "postgres", + "expected_names": [ + "Postgres", + "PostgreSQL" + ], + "source_code_url": "https://github.com/postgres/postgres", + "clean_name": "PostgreSQL" + }, + { + "slug": "actual-budget", + "expected_names": [ + "Actual Budget" + ], + "source_code_url": "https://github.com/actualbudget/actual" + }, + { + "slug": "solr", + "expected_names": [ + "Apache Solr" + ], + "source_code_url": "https://github.com/apache/solr" + }, + { + "slug": "babybuddy", + "expected_names": [ + "babybuddy", + "Baby Buddy" + ], + "source_code_url": "https://github.com/babybuddy/babybuddy", + "clean_name": "Baby Buddy" + }, + { + "slug": "flowise-ai", + "expected_names": [ + "Flowise" + ], + "source_code_url": "https://github.com/FlowiseAI/Flowise" + }, + { + "slug": "lyrion-music-server", + "expected_names": [ + "Lyrion Music Server" + ], + "source_code_url": "https://github.com/LMS-Community/slimserver" + }, + { + "slug": "nginxproxymanager", + "expected_names": [ + "Nginx Proxy Manager" + ], + "source_code_url": "https://github.com/NginxProxyManager/nginx-proxy-manager" + }, + { + "slug": "hydra2", + "expected_names": [ + "NZBHydra 2" + ], + "source_code_url": "https://github.com/theotherp/nzbhydra2" + } + ], + "records": [ + { + "slug": "immich-redis", + "kind": "dependency_container", + "name": "immich-redis", + "source_code_url": "https://github.com/valkey-io/valkey-container" + }, + { + "slug": "immich-postgres", + "kind": "dependency_container", + "name": "immich-postgres", + "source_code_url": "https://github.com/postgres/postgres" + }, + { + "slug": "lucia-postgresql", + "kind": "dependency_container", + "name": "Lucia-PostgreSQL", + "source_code_url": "https://github.com/seiggy/lucia-dotnet" + }, + { + "slug": "makerworks-postgres", + "kind": "dependency_container", + "name": "makerworks-postgres", + "source_code_url": "https://github.com/schartrand77/makerworks-unraid-templates" + }, + { + "slug": "spamfilter-redis", + "kind": "dependency_container", + "name": "spamfilter-redis", + "source_code_url": "https://github.com/marcelverdult/imap-spamfilter" + }, + { + "slug": "tubearchivist-redis", + "kind": "dependency_container", + "name": "TubeArchivist-Redis", + "source_code_url": "https://github.com/redis/redis" + }, + { + "slug": "onlyoffice-mysql-server", + "kind": "dependency_container", + "name": "onlyoffice-mysql-server", + "source_code_url": null + }, + { + "slug": "docker-in-docker-dind", + "kind": "dependency_container", + "name": "Docker-in-Docker (DinD)", + "source_code_url": "https://github.com/docker-library/docker" + }, + { + "slug": "couchdb-offical-docker-v1-x", + "kind": "image_variant", + "name": "CouchDB-Offical-Docker-v1.x", + "source_code_url": "https://github.com/docker-library/official-images", + "canonical_slug": "couchdb" + }, + { + "slug": "couchdb-v2-x", + "kind": "image_variant", + "name": "CouchDB-v2.x", + "source_code_url": "https://github.com/apache/couchdb-docker", + "canonical_slug": "couchdb" + }, + { + "slug": "elasticsearch7", + "kind": "image_variant", + "name": "Elasticsearch7", + "source_code_url": "https://github.com/elastic/elasticsearch", + "canonical_slug": "elasticsearch" + }, + { + "slug": "elasticsearch8", + "kind": "image_variant", + "name": "Elasticsearch8", + "source_code_url": "https://github.com/elastic/elasticsearch", + "canonical_slug": "elasticsearch" + }, + { + "slug": "postgresql18", + "kind": "image_variant", + "name": "PostgreSQL 18", + "source_code_url": "https://github.com/postgres/postgres", + "canonical_slug": "postgres" + }, + { + "slug": "elastic-search", + "kind": "duplicate", + "name": "Elastic Search", + "source_code_url": "https://github.com/elastic/elasticsearch", + "canonical_slug": "elasticsearch" + }, + { + "slug": "actualserver", + "kind": "duplicate", + "name": "Actual Budget", + "source_code_url": "https://github.com/actualbudget/actual", + "canonical_slug": "actual-budget" + }, + { + "slug": "apache-solr", + "kind": "duplicate", + "name": "Apache Solr", + "source_code_url": "https://github.com/apache/solr", + "canonical_slug": "solr" + }, + { + "slug": "baby-buddy", + "kind": "duplicate", + "name": "Baby Buddy", + "source_code_url": "https://github.com/babybuddy/babybuddy", + "canonical_slug": "babybuddy" + }, + { + "slug": "flowise", + "kind": "duplicate", + "name": "Flowise", + "source_code_url": "https://github.com/FlowiseAI/Flowise", + "canonical_slug": "flowise-ai" + }, + { + "slug": "lyrionmusicserver", + "kind": "duplicate", + "name": "LyrionMusicServer", + "source_code_url": "https://github.com/LMS-Community/slimserver", + "canonical_slug": "lyrion-music-server" + }, + { + "slug": "nginx-proxy-manager", + "kind": "duplicate", + "name": "Nginx Proxy Manager", + "source_code_url": "https://github.com/NginxProxyManager/nginx-proxy-manager", + "canonical_slug": "nginxproxymanager" + }, + { + "slug": "nzbhydra2", + "kind": "duplicate", + "name": "NZBHydra 2", + "source_code_url": "https://github.com/theotherp/nzbhydra2", + "canonical_slug": "hydra2" + } + ] +} diff --git a/scripts/self-host-apps/lib/classification.mjs b/scripts/self-host-apps/lib/classification.mjs index 43026afa..d3dd481e 100644 --- a/scripts/self-host-apps/lib/classification.mjs +++ b/scripts/self-host-apps/lib/classification.mjs @@ -7,6 +7,41 @@ const duplicateAliasSlugs = new Set([ "elastic-search", "actualserver", "apache-solr", "baby-buddy", "flowise", "lyrionmusicserver", "nginx-proxy-manager", "nzbhydra2", ]); +const canonicalProductSlugs = new Map([ + ["postgres", /^postgres(?:ql)?(?:\s+\d+(?:\.\d+)*)?$/i], + ["postgresql", /^postgres(?:ql)?(?:\s+\d+(?:\.\d+)*)?$/i], + ["elasticsearch", /^elasticsearch(?:\s+\d+(?:\.\d+)*)?$/i], + ["couchdb", /^(?:apache\s+)?couchdb(?:\s+\d+(?:\.\d+)*)?$/i], + ["redis", /^redis(?:\s+\d+(?:\.\d+)*)?$/i], + ["valkey", /^valkey(?:\s+\d+(?:\.\d+)*)?$/i], + ["mysql", /^mysql(?:\s+\d+(?:\.\d+)*)?$/i], + ["mariadb", /^mariadb(?:\s+\d+(?:\.\d+)*)?$/i], + ["mongodb", /^mongodb(?:\s+\d+(?:\.\d+)*)?$/i], + ["rabbitmq", /^rabbitmq(?:\s+\d+(?:\.\d+)*)?$/i], + ["memcached", /^memcached(?:\s+\d+(?:\.\d+)*)?$/i], + ["neo4j", /^neo4j(?:\s+\d+(?:\.\d+)*)?$/i], +]); + +function isCanonicalProductIdentity(slug, name) { + return canonicalProductSlugs.get(slug)?.test(name) ?? false; +} + +function hasStrongVersionVariantEvidence(record) { + const slug = String(record.slug ?? "").toLowerCase(); + const name = String(record.name ?? "").toLowerCase(); + const source = String(record.source_code_url ?? "").replace(/\/$/, "").toLowerCase(); + const image = String(record.docker_image ?? "").toLowerCase(); + + if (/\b(?:official|offical)[ -]docker\b/.test(`${slug} ${name}`) && + source === "https://github.com/docker-library/official-images" && + /couchdb/.test(`${slug} ${name}`) && /^(?:docker\.io\/)?(?:apache\/)?couchdb(?::|$)/.test(image)) return true; + if (/^couchdb-v\d+-x$/.test(slug) && /couchdb/.test(name) && + source === "https://github.com/apache/couchdb-docker" && + /^(?:docker\.io\/)?(?:apache\/)?couchdb(?::|$)/.test(image)) return true; + if (/^elasticsearch\d+$/.test(slug) && /^elasticsearch\s*\d+$/.test(name) && source === "https://github.com/elastic/elasticsearch") return true; + if (/^postgresql\d+$/.test(slug) && /^postgresql\s*\d+$/.test(name) && source === "https://github.com/postgres/postgres" && /^postgres:\d/.test(image)) return true; + return false; +} /** Classify only records with direct evidence that they are not standalone apps. */ export function classifySelfHostRecord(record = {}) { @@ -16,6 +51,11 @@ export function classifySelfHostRecord(record = {}) { const image = String(record.docker_image ?? "").trim(); const identity = `${slug} ${name}`; + // Versioned canonical names such as PostgreSQL 18 remain the same product. + if (isCanonicalProductIdentity(slug, String(record.name ?? "").trim())) { + return { kind: "application", reason: "canonical product identity, including its versioned display name" }; + } + if (duplicateAliasSlugs.has(slug)) { return { kind: "duplicate", reason: "reviewed duplicate slug for an existing app record" }; } @@ -37,8 +77,8 @@ export function classifySelfHostRecord(record = {}) { return { kind: "dependency_container", reason: "app-branded name uses a generic database or cache image" }; } - if (/\b(?:official|offical)[ -]docker\b|\b(?:couchdb|elasticsearch|postgres(?:ql)?)[- ]?(?:v)?\d+(?:[.-]x)?\b/i.test(`${slug} ${name}`)) { - return { kind: "image_variant", reason: "record names an upstream image or version rather than a distinct product" }; + if (hasStrongVersionVariantEvidence(record)) { + return { kind: "image_variant", reason: "record is an upstream image/version variant of a canonical product" }; } return { kind: "application", reason: "no direct artifact evidence matched" }; diff --git a/scripts/self-host-apps/scripts.json b/scripts/self-host-apps/scripts.json index d1a8b6a3..686097d3 100644 --- a/scripts/self-host-apps/scripts.json +++ b/scripts/self-host-apps/scripts.json @@ -215,6 +215,11 @@ "command": "node scripts/self-host-apps/apply-qa-overrides.mjs --file scripts/self-host-apps/batches/YYYY-MM-DD-qa-reviewed.json --dry-run", "purpose": "Apply reviewed app corrections and append their source URLs." }, + { + "name": "self-host-apps:apply-artifact-cleanup", + "command": "npm run self-host-apps:apply-artifact-cleanup -- --environment development", + "purpose": "Preview the reviewed helper-container, image-variant, and duplicate cleanup manifest; requires --apply to write." + }, { "name": "self-host-apps:sync-prod-to-local", "command": "npm run self-host-apps:sync-prod-to-local -- --confirm-local-reset", diff --git a/scripts/self-host-apps/sync-local-to-prod.mjs b/scripts/self-host-apps/sync-local-to-prod.mjs index 26d7afd4..e1e1592c 100644 --- a/scripts/self-host-apps/sync-local-to-prod.mjs +++ b/scripts/self-host-apps/sync-local-to-prod.mjs @@ -5,6 +5,7 @@ import pg from "pg"; import { syncRecords, changedColumns } from '../stack127/database/sync-records.mjs'; import { parseArgs, numberArg } from "./lib/args.mjs"; import { appColumns } from "./lib/schema.mjs"; +import { selfHostRecordClassificationError } from "./lib/classification.mjs"; const { Pool } = pg; @@ -141,6 +142,13 @@ async function main() { const prodClient = await prodPool.connect(); try { const state = await fetchLocalState(localClient); + const blockedPublishedApps = state.apps + .filter((app) => app.published) + .map((app) => ({ slug: app.slug, error: selfHostRecordClassificationError(app) })) + .filter((app) => app.error); + if (blockedPublishedApps.length > 0) { + throw new Error(`Refusing to sync blocked published self-host apps: ${blockedPublishedApps.map((app) => `${app.slug} (${app.error})`).join(", ")}`); + } await prodClient.query("begin"); await upsertCategories(prodClient, state.categories); await upsertApps(prodClient, state.apps); diff --git a/stack127_docs/self-host-apps-data.md b/stack127_docs/self-host-apps-data.md index cfc5f881..f7ae21bc 100644 --- a/stack127_docs/self-host-apps-data.md +++ b/stack127_docs/self-host-apps-data.md @@ -68,4 +68,29 @@ Other providers keep using the existing `source_*` columns until a provider tabl They still let QA tell unchecked data apart from an official source that published no value. +## Reviewed artifact cleanup + +The cleanup manifest records reviewed helper containers, image variants, and duplicate app rows. Preview it before a change: + +```bash +npm run self-host-apps:apply-artifact-cleanup -- --environment development +``` + +Preview is read-only and is the default. It shows each target row's name, source, image, publication state, and whether it needs cleanup. The command stops if a target or canonical app no longer matches the expected slug, name, and source. + +Apply to development with: + +```bash +npm run self-host-apps:apply-artifact-cleanup -- --environment development --apply +``` + +The apply runs in one transaction. It keeps source links on canonical app rows, fixes reviewed product names, and unpublishes artifact rows. Re-running it accepts rows that are already clean. Production uses the same reviewed manifest, but requires an explicit confirmation flag: + +```bash +npm run self-host-apps:apply-artifact-cleanup -- --environment production +npm run self-host-apps:apply-artifact-cleanup -- --environment production --apply --confirm-reviewed-cleanup +``` + +Review the production preview before running the apply command. + The migration `20260901060529_streamline_self_host_apps_data_model.sql` folds the old rows into these tables. It removes pipeline-only tables and compatibility views and keeps the candidate review queue. diff --git a/supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql b/supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql deleted file mode 100644 index 80571dd2..00000000 --- a/supabase/migrations/20261101090000_clean_self_host_app_artifacts.sql +++ /dev/null @@ -1,78 +0,0 @@ -begin; - -create temporary table self_host_app_cleanup on commit drop as -select slug, canonical_slug, classification -from (values - ('immich-redis', null, 'dependency_container'), - ('immich-postgres', null, 'dependency_container'), - ('lucia-postgresql', null, 'dependency_container'), - ('makerworks-postgres', null, 'dependency_container'), - ('spamfilter-redis', null, 'dependency_container'), - ('tubearchivist-redis', null, 'dependency_container'), - ('onlyoffice-mysql-server', null, 'dependency_container'), - ('couchdb-offical-docker-v1-x', 'couchdb', 'image_variant'), - ('couchdb-v2-x', 'couchdb', 'image_variant'), - ('elastic-search', 'elasticsearch', 'duplicate'), - ('elasticsearch7', 'elasticsearch', 'image_variant'), - ('elasticsearch8', 'elasticsearch', 'image_variant'), - ('postgresql18', 'postgres', 'image_variant') -) as records(slug, canonical_slug, classification); - -create temporary table self_host_app_cleanup_count on commit drop as -select count(*)::integer as published_before from public.self_host_apps -where published and self_hosting_status='self_hostable'; - -do $$ -begin - if (select count(*) from self_host_app_cleanup) <> 13 then - raise exception 'Expected 13 reviewed records in cleanup manifest'; - end if; - if (select count(*) from public.self_host_apps app join self_host_app_cleanup c using (slug) - where app.published and app.self_hosting_status = 'self_hostable') <> 13 then - raise exception 'Cleanup snapshot changed: expected all 13 records to remain published and self-hostable'; - end if; - if (select count(distinct app.id) from self_host_app_cleanup c join public.self_host_apps app on app.slug=c.canonical_slug - where c.canonical_slug is not null and app.published and app.self_hosting_status='self_hostable') <> 3 then - raise exception 'A canonical app record is missing or unpublished'; - end if; -end $$; - --- Keep source links from every artifact record on its canonical app before unpublishing it. -update public.self_host_apps canonical -set data_source_urls = ( - select array_agg(distinct merged.url order by merged.url) - from unnest( - coalesce(canonical.data_source_urls, '{}') || coalesce(artifacts.urls, '{}') - ) as merged(url) -), updated_at = now() -from ( - select c.canonical_slug, array_agg(distinct item.url) filter (where item.url is not null) as urls - from self_host_app_cleanup c - join public.self_host_apps artifact on artifact.slug=c.slug - cross join lateral unnest(array[artifact.source_code_url, artifact.website_url, artifact.docs_url, - artifact.install_docs_url, artifact.docker_url]) as item(url) - where c.canonical_slug is not null - group by c.canonical_slug -) artifacts -where canonical.slug=artifacts.canonical_slug; - -update public.self_host_apps -set published=false, updated_at=now() -where slug in (select slug from self_host_app_cleanup); - -update public.self_host_apps set name='CouchDB', updated_at=now() where slug='couchdb'; -update public.self_host_apps set name='Elasticsearch', updated_at=now() where slug='elasticsearch'; -update public.self_host_apps set name='PostgreSQL', updated_at=now() where slug='postgres'; - -do $$ -begin - if (select count(*) from public.self_host_apps where slug in (select slug from self_host_app_cleanup) and not published) <> 13 then - raise exception 'Cleanup did not unpublish all reviewed artifacts'; - end if; - if (select count(*) from public.self_host_apps where published and self_hosting_status='self_hostable') <> - (select published_before - 13 from self_host_app_cleanup_count) then - raise exception 'Unexpected cleaned published count; expected a reduction of exactly 13'; - end if; -end $$; - -commit; diff --git a/supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql b/supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql deleted file mode 100644 index a79985f5..00000000 --- a/supabase/migrations/20261101091500_unpublish_docker_in_docker_helper.sql +++ /dev/null @@ -1,15 +0,0 @@ -begin; - -do $$ -begin - if (select count(*) from public.self_host_apps - where slug='docker-in-docker-dind' and published and self_hosting_status='self_hostable') <> 1 then - raise exception 'Docker-in-Docker cleanup snapshot changed'; - end if; -end $$; - -update public.self_host_apps -set published=false, updated_at=now() -where slug='docker-in-docker-dind'; - -commit; diff --git a/supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql b/supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql deleted file mode 100644 index d381cbba..00000000 --- a/supabase/migrations/20261101093000_merge_exact_self_host_app_duplicates.sql +++ /dev/null @@ -1,53 +0,0 @@ -begin; - -create temporary table self_host_app_duplicate_cleanup on commit drop as -select slug, canonical_slug -from (values - ('actualserver', 'actual-budget'), - ('apache-solr', 'solr'), - ('baby-buddy', 'babybuddy'), - ('flowise', 'flowise-ai'), - ('lyrionmusicserver', 'lyrion-music-server'), - ('nginx-proxy-manager', 'nginxproxymanager'), - ('nzbhydra2', 'hydra2') -) as records(slug, canonical_slug); - -do $$ -begin - if (select count(*) from self_host_app_duplicate_cleanup) <> 7 then - raise exception 'Expected seven exact duplicate pairs in cleanup manifest'; - end if; - if (select count(*) from public.self_host_apps app join self_host_app_duplicate_cleanup c using (slug) - where app.published and app.self_hosting_status='self_hostable') <> 7 then - raise exception 'Duplicate cleanup snapshot changed'; - end if; - if (select count(distinct app.id) from self_host_app_duplicate_cleanup c - join public.self_host_apps app on app.slug=c.canonical_slug - where app.published and app.self_hosting_status='self_hostable') <> 7 then - raise exception 'A canonical app record is missing or unpublished'; - end if; -end $$; - -update public.self_host_apps canonical -set data_source_urls=( - select array_agg(distinct merged.url order by merged.url) - from unnest(coalesce(canonical.data_source_urls,'{}') || coalesce(duplicate.urls,'{}')) as merged(url) -), updated_at=now() -from ( - select c.canonical_slug, - array_agg(distinct item.url) filter (where item.url is not null) as urls - from self_host_app_duplicate_cleanup c - join public.self_host_apps source on source.slug=c.slug - cross join lateral unnest(array[source.source_code_url,source.website_url,source.docs_url, - source.install_docs_url,source.docker_url]) as item(url) - group by c.canonical_slug -) duplicate -where canonical.slug=duplicate.canonical_slug; - -update public.self_host_apps -set published=false, updated_at=now() -where slug in (select slug from self_host_app_duplicate_cleanup); - -update public.self_host_apps set name='Baby Buddy', updated_at=now() where slug='babybuddy'; - -commit; diff --git a/tests/self-host-apps/pipeline.test.mjs b/tests/self-host-apps/pipeline.test.mjs index 09a03fed..49015a24 100644 --- a/tests/self-host-apps/pipeline.test.mjs +++ b/tests/self-host-apps/pipeline.test.mjs @@ -116,12 +116,15 @@ test("unclassified records default self-hosting status to unknown", () => { test("classification blocks dependency containers and image variants with direct evidence", () => { assert.equal(classifySelfHostRecord({ slug: "immich-redis", name: "immich-redis", summary: "Redis-compatible Valkey service used by Immich for background task queues.", docker_image: "valkey/valkey:9" }).kind, "dependency_container"); assert.equal(classifySelfHostRecord({ slug: "makerworks-postgres", name: "makerworks-postgres", summary: "PostgreSQL database for the MakerWorks suite.", docker_image: "postgres:15" }).kind, "dependency_container"); - assert.equal(classifySelfHostRecord({ slug: "couchdb-offical-docker-v1-x", name: "CouchDB-Offical-Docker-v1.x" }).kind, "image_variant"); - assert.equal(classifySelfHostRecord({ slug: "couchdb-v2-x", name: "CouchDB-v2.x" }).kind, "image_variant"); + assert.equal(classifySelfHostRecord({ slug: "couchdb-offical-docker-v1-x", name: "CouchDB-Offical-Docker-v1.x" }).kind, "application"); + assert.equal(classifySelfHostRecord({ slug: "couchdb-offical-docker-v1-x", name: "CouchDB-Offical-Docker-v1.x", source_code_url: "https://github.com/docker-library/official-images", docker_image: "couchdb" }).kind, "image_variant"); + assert.equal(classifySelfHostRecord({ slug: "couchdb-v2-x", name: "CouchDB-v2.x" }).kind, "application"); + assert.equal(classifySelfHostRecord({ slug: "couchdb-v2-x", name: "CouchDB-v2.x", source_code_url: "https://github.com/apache/couchdb-docker", docker_image: "apache/couchdb" }).kind, "image_variant"); assert.equal(classifySelfHostRecord({ slug: "docker-in-docker-dind", name: "Docker-in-Docker", summary: "Runs a separate Docker daemon inside a container so build tools can create images.", docker_image: "docker:dind" }).kind, "dependency_container"); assert.equal(classifySelfHostRecord({ slug: "elastic-search", name: "Elastic Search" }).kind, "duplicate"); assert.equal(classifySelfHostRecord({ slug: "actualserver", name: "Actual Budget" }).kind, "duplicate"); assert.equal(classifySelfHostRecord({ slug: "sample-deployment", name: "Sample Deployment", summary: "Example stack for a demo deployment." }).kind, "example_stack"); + assert.equal(classifySelfHostRecord({ slug: "unrelated-tool", name: "Official Docker v1.x Helper" }).kind, "application"); const dependency = { slug: "lucia-postgresql", name: "Lucia-PostgreSQL", summary: "PostgreSQL companion for Lucia.", docker_image: "postgres:17", published: true }; assert.match(selfHostRecordClassificationError(dependency), /dependency_container/); assert.ok(validateRecord(dependency).errors.some((error) => /dependency_container/.test(error))); @@ -135,3 +138,17 @@ test("classification keeps standalone services and companion apps publishable", { slug: "pict-rs", name: "Pict-rs", summary: "Image hosting service used by Lemmy for storing and serving uploaded pictures.", docker_image: "asonix/pictrs:latest" }, ]) assert.equal(classifySelfHostRecord(record).kind, "application", record.slug); }); + +test("classification keeps canonical products when their names include a version", () => { + for (const record of [ + { slug: "postgres", name: "PostgreSQL 18" }, + { slug: "postgresql", name: "PostgreSQL 18.1" }, + { slug: "elasticsearch", name: "Elasticsearch 8" }, + { slug: "couchdb", name: "CouchDB 3" }, + { slug: "redis", name: "Redis 7" }, + { slug: "mysql", name: "MySQL 8" }, + { slug: "mariadb", name: "MariaDB 11" }, + { slug: "mongodb", name: "MongoDB 8" }, + { slug: "rabbitmq", name: "RabbitMQ 4" }, + ]) assert.equal(classifySelfHostRecord(record).kind, "application", `${record.slug}: ${record.name}`); +}); From 5bb19e8af3ddfea1671d877f22ce41ed899f56e8 Mon Sep 17 00:00:00 2001 From: RaviTejaKNTS Date: Sun, 1 Nov 2026 14:41:30 +0530 Subject: [PATCH 9/9] Use the shared database resolver and confirm gate for self-host cleanup Co-Authored-By: Claude Opus 5.5 --- .../self-host-apps/apply-artifact-cleanup.mjs | 85 ++++++------------- stack127_docs/self-host-apps-data.md | 6 +- .../self-host-apps/artifact-cleanup.test.mjs | 44 ++++++++++ 3 files changed, 73 insertions(+), 62 deletions(-) create mode 100644 tests/self-host-apps/artifact-cleanup.test.mjs diff --git a/scripts/self-host-apps/apply-artifact-cleanup.mjs b/scripts/self-host-apps/apply-artifact-cleanup.mjs index 1ed99cc1..f91eb4dc 100644 --- a/scripts/self-host-apps/apply-artifact-cleanup.mjs +++ b/scripts/self-host-apps/apply-artifact-cleanup.mjs @@ -1,54 +1,29 @@ #!/usr/bin/env node -import { readFileSync } from "node:fs"; import { readFile } from "node:fs/promises"; +import { pathToFileURL } from "node:url"; import { withClient } from "./lib/db.mjs"; import { parseArgs } from "./lib/args.mjs"; +import { resolveDatabaseUrl } from "../stack127/tool-pages/lib.mjs"; const manifestPath = new URL("./artifact-cleanup-manifest.json", import.meta.url); -const developmentProjectRef = "yzasonowwlvritqicatj"; -function readEnvFile(path) { - const source = requireEnvText(path); - return Object.fromEntries(source.split(/\r?\n/).flatMap((line) => { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith("#")) return []; - const index = trimmed.indexOf("="); - if (index < 0) return []; - return [[trimmed.slice(0, index).trim(), trimmed.slice(index + 1).trim().replace(/^['"]|['"]$/g, "")]]; - })); -} - -function requireEnvText(path) { - try { - return readFileSync(path, "utf8"); - } catch { - throw new Error(`Could not read ${path}. Run this command from the repository root.`); - } -} - -function databaseConfig(environment) { - const file = environment === "production" ? ".env.database" : ".env.local"; - const env = readEnvFile(file); - const siteUrl = new URL(env.SUPABASE_URL ?? ""); - const projectRef = siteUrl.hostname.endsWith(".supabase.co") - ? siteUrl.hostname.slice(0, -".supabase.co".length) - : ""; - const connectionString = env.DATABASE_POOLER_URL || env.DATABASE_URL; - if (!connectionString || !projectRef) throw new Error(`${file} must contain SUPABASE_URL and a database URL.`); - - const connection = new URL(connectionString); - const username = decodeURIComponent(connection.username); - const isDirect = connection.hostname === `db.${projectRef}.supabase.co`; - const isPooler = connection.hostname.endsWith(".pooler.supabase.com") && username.includes(projectRef); - if (!isDirect && !isPooler) throw new Error(`${file} database URL does not match its Supabase project URL.`); - if (environment === "development" && projectRef !== developmentProjectRef) { - throw new Error("Development cleanup requires the managed development project in .env.local."); +/** + * Check the flags and pick the database with the shared resolver: `.env.local` for + * development, `.env.database` for production. Production needs the shared confirm + * value even for a preview, so a preview can't reach production by accident. + */ +export function cleanupOptions(args) { + const environment = args.environment; + if (!["development", "production"].includes(environment)) { + throw new Error("Pass --environment development or --environment production."); } - if (environment === "production" && projectRef === developmentProjectRef) { - throw new Error("Production cleanup refuses the development project."); + if (environment === "production" && args.confirm !== "stack127-production") { + throw new Error("Production requires --confirm stack127-production, including previews."); } - return connectionString; + // The resolver rejects a value on --apply, such as --apply=false. + const databaseUrl = resolveDatabaseUrl({ environment, apply: args.apply, confirm: args.confirm }); + return { environment, apply: args.apply === true, databaseUrl }; } function identityMatches(row, expected) { @@ -157,28 +132,18 @@ async function applyCleanup(client, manifest, targetRows, canonicalRows) { async function main() { const args = parseArgs(); - const environment = args.environment; - if (!["development", "production"].includes(environment)) { - throw new Error("Pass --environment development or --environment production."); - } - if (args.apply && environment === "production" && !args["confirm-reviewed-cleanup"]) { - throw new Error("Production apply requires --confirm-reviewed-cleanup."); - } - if (args["confirm-reviewed-cleanup"] && environment !== "production") { - throw new Error("--confirm-reviewed-cleanup is only valid with --environment production."); - } + const { environment, apply, databaseUrl } = cleanupOptions(args); const manifest = JSON.parse(await readFile(manifestPath, "utf8")); - const databaseUrl = databaseConfig(environment); await withClient(async (client) => { - await client.query(args.apply ? "begin isolation level serializable" : "begin read only"); + await client.query(apply ? "begin isolation level serializable" : "begin read only"); try { - const { targets, canonicals } = await loadRows(client, manifest, Boolean(args.apply)); + const { targets, canonicals } = await loadRows(client, manifest, apply); const states = validateIdentities(manifest, targets, canonicals); console.log(`Artifact cleanup preview (${environment})`); for (const state of states) console.log(JSON.stringify(state)); const needed = states.filter((state) => state.state !== "already_clean"); - if (!args.apply) { + if (!apply) { await client.query("rollback"); console.log(`Preview only: ${needed.length} record(s) need cleanup; ${states.length - needed.length} already clean.`); return; @@ -198,7 +163,9 @@ async function main() { }, databaseUrl); } -main().catch((error) => { - console.error(error.stack ?? error.message); - process.exit(1); -}); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + console.error(error.stack ?? error.message); + process.exit(1); + }); +} diff --git a/stack127_docs/self-host-apps-data.md b/stack127_docs/self-host-apps-data.md index f7ae21bc..e7e09150 100644 --- a/stack127_docs/self-host-apps-data.md +++ b/stack127_docs/self-host-apps-data.md @@ -84,11 +84,11 @@ Apply to development with: npm run self-host-apps:apply-artifact-cleanup -- --environment development --apply ``` -The apply runs in one transaction. It keeps source links on canonical app rows, fixes reviewed product names, and unpublishes artifact rows. Re-running it accepts rows that are already clean. Production uses the same reviewed manifest, but requires an explicit confirmation flag: +The apply runs in one transaction. It keeps source links on canonical app rows, fixes reviewed product names, and unpublishes artifact rows. Re-running it accepts rows that are already clean. Production uses the same reviewed manifest, and needs `--confirm stack127-production` for both the preview and the apply: ```bash -npm run self-host-apps:apply-artifact-cleanup -- --environment production -npm run self-host-apps:apply-artifact-cleanup -- --environment production --apply --confirm-reviewed-cleanup +npm run self-host-apps:apply-artifact-cleanup -- --environment production --confirm stack127-production +npm run self-host-apps:apply-artifact-cleanup -- --environment production --confirm stack127-production --apply ``` Review the production preview before running the apply command. diff --git a/tests/self-host-apps/artifact-cleanup.test.mjs b/tests/self-host-apps/artifact-cleanup.test.mjs new file mode 100644 index 00000000..95269b5d --- /dev/null +++ b/tests/self-host-apps/artifact-cleanup.test.mjs @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { cleanupOptions } from "../../scripts/self-host-apps/apply-artifact-cleanup.mjs"; + +const developmentUrl = "postgresql://postgres.devref:secret@aws-0-eu.pooler.supabase.com:6543/postgres"; +const productionUrl = "postgresql://stack127:secret@database.stack127.com:5432/stack127"; + +function withEnvFiles(callback) { + const previous = process.cwd(); + const dir = mkdtempSync(join(tmpdir(), "artifact-cleanup-")); + writeFileSync(join(dir, ".env.local"), `SUPABASE_URL=https://devref.supabase.co\nDATABASE_URL=${developmentUrl}\n`); + writeFileSync(join(dir, ".env.database"), `SUPABASE_URL=https://api.stack127.com\nDATABASE_URL=${productionUrl}\n`); + process.chdir(dir); + try { + return callback(); + } finally { + process.chdir(previous); + rmSync(dir, { recursive: true, force: true }); + } +} + +test("production cleanup uses the self-hosted production database", () => { + withEnvFiles(() => { + const options = cleanupOptions({ environment: "production", confirm: "stack127-production", apply: true }); + assert.equal(options.databaseUrl, productionUrl); + assert.equal(options.apply, true); + }); +}); + +test("development cleanup uses the development database", () => { + withEnvFiles(() => { + assert.equal(cleanupOptions({ environment: "development" }).databaseUrl, developmentUrl); + }); +}); + +test("production cleanup needs the shared confirm value, even for a preview", () => { + withEnvFiles(() => { + assert.throws(() => cleanupOptions({ environment: "production" }), /stack127-production/); + assert.throws(() => cleanupOptions({ environment: "production", apply: true, confirm: "yes" }), /stack127-production/); + }); +});