Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/(app)/dashboard/projects/[id]/repos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
getOrMintInstallationToken,
} from "@/lib/github/installations";
import { listInstallationRepos } from "@/lib/github/app";
import { DEFAULT_PROJECT_ENGINES, type Engine } from "@/lib/credits";
import { DEFAULT_PROJECT_ENGINES, SCAN_CREDITS, SCAN_RACK_CENTS, dollars, type Engine } from "@/lib/credits";
import type { ProjectStatus } from "@/app/actions/projects";
import { AddRepoModal } from "./add-repo-modal";
import { RemoveButton } from "./remove-button";
Expand Down Expand Up @@ -190,8 +190,8 @@ export default async function ProjectReposPage({
</li>
<li>
<strong>Apply Fix</strong> (audit findings) opens a Claude-
authored PR that patches one specific check. Costs 20 credits
(~$1) per run; refunded on failure.
authored PR that patches one specific check. Costs {SCAN_CREDITS} credits
({dollars(SCAN_RACK_CENTS)} before volume discounts) per run; refunded on failure.
</li>
<li>
<strong>Future:</strong> webhook-triggered audits on push to
Expand Down
4 changes: 3 additions & 1 deletion app/(app)/dashboard/settings/billing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { PurchaseStatusBanner } from "./purchase-status-banner";
import {
CREDIT_PACKS,
CREDIT_RACK_CENTS,
SCAN_CREDITS,
SCAN_RACK_CENTS,
discountPct,
dollars,
perScanCents,
Expand Down Expand Up @@ -78,7 +80,7 @@ export default async function BillingPage({
<span className="text-[var(--color-muted)]">credits</span>
</div>
<div className="mt-2 text-xs text-[var(--color-muted)]">
1 AI-model scan = 20 credits (~$1). Scheduled re-runs spend the same per engine.
1 AI-model scan = {SCAN_CREDITS} credits ({dollars(SCAN_RACK_CENTS)} before volume discounts). Scheduled re-runs spend the same per engine.
</div>
</div>

Expand Down
14 changes: 7 additions & 7 deletions app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Link from "next/link";
import { HeroAuditForm } from "@/components/hero-audit-form";
import { ANON_DAILY_SCANS, SCAN_CREDITS, SIGNUP_CREDITS } from "@/lib/credits";
import { ANON_DAILY_SCANS, SCAN_CREDITS, SIGNUP_CREDITS, SCAN_RACK_CENTS, SCAN_MIN_CENTS, dollars } from "@/lib/credits";
import { OrganizationJsonLd, SoftwareApplicationJsonLd, FaqJsonLd } from "@/components/json-ld";
import { PerformancePreview } from "@/components/report/performance-preview";
import { UptimePreview } from "@/components/report/uptime-preview";
Expand Down Expand Up @@ -34,7 +34,7 @@ const faqs = [
},
{
q: "Is the free tier really free?",
a: `Yes — anonymous visitors get ${ANON_DAILY_SCANS} audits per day per IP with no signup, and signing up unlocks ${SIGNUP_CREDITS} free credits (${SIGNUP_CREDITS / SCAN_CREDITS} AI-model scan). Paid scans cost ${SCAN_CREDITS} credits (~$1) with volume discounts down to $0.50/scan at the 100-scan pack. No subscription, credits never expire.`,
a: `Yes — anonymous visitors get ${ANON_DAILY_SCANS} audits per day per IP with no signup, and signing up unlocks ${SIGNUP_CREDITS} free credits (${SIGNUP_CREDITS / SCAN_CREDITS} AI-model scan). Paid scans cost ${SCAN_CREDITS} credits (${dollars(SCAN_RACK_CENTS)} at standard price) with volume discounts down to ${dollars(SCAN_MIN_CENTS)}/scan at the 100-scan pack. No subscription, credits never expire.`,
},
];

Expand Down Expand Up @@ -106,7 +106,7 @@ export default function HomePage() {
<ModuleCard emoji="🔍" title="SEO · AEO · GEO Audits" body="See your site the way ChatGPT, Claude, Perplexity, and Google AI crawlers do — with a priority to-do list." />
<ModuleCard emoji="📈" title="Uptime Monitoring" body="HTTP, keyword, SSL &amp; TCP checks with instant down/recovery alerts to email, Slack, and Discord." />
<ModuleCard emoji="🛡️" title="Exposed Services" body="Full-port security scans of your verified hosts — get alerted the moment a database or admin port is exposed." />
<ModuleCard emoji="✍️" title="Autoblog" body="Research, draft, illustrate, and publish long-form SEO posts to your CMS on a schedule — ~$1 each." />
<ModuleCard emoji="✍️" title="Autoblog" body={`Research, draft, illustrate, and publish long-form SEO posts to your CMS on a schedule — ${dollars(SCAN_RACK_CENTS)} each before volume discounts.`} />
<ModuleCard emoji="🎯" title="Lead Generation" body="Find the companies, the people and the addresses from any directory or search — then send grounded cold email from your own mailbox." href="/lead-generation" />
<ModuleCard emoji="🔗" title="Link Exchange" body="Verified backlink matching with a real ledger — quality links without four-figure marketplace prices." />
<ModuleCard emoji="📊" title="Analytics Tracker" body="A drop-in tracker for AI referrals, bot crawls, human traffic, pages, and geo — see who (and what) visits." />
Expand Down Expand Up @@ -240,7 +240,7 @@ export default function HomePage() {
<p className="mt-4 text-[var(--color-muted)]">
Connect a webhook once and CrawlProof can research topics, draft long-form
posts, attach a hero image, and deliver articles to your CMS on schedule.
Each published post uses 20 credits (~$1).
Each published post uses {SCAN_CREDITS} credits ({dollars(SCAN_RACK_CENTS)} before volume discounts).
</p>
<div className="mt-6 grid gap-3 sm:grid-cols-3">
<LaunchStat label="Cost model" value="20 credits" />
Expand Down Expand Up @@ -362,7 +362,7 @@ function AutoblogLaunchGraphic() {
return (
<div
className="rounded-xl border border-[var(--color-border)] bg-[#101820] p-5 shadow-2xl shadow-black/20 sm:p-6"
aria-label="Backlink marketplace examples ranged from $445 to $4,090, compared with CrawlProof Autoblog at 20 credits (~$1) per post."
aria-label={`Backlink marketplace examples ranged from $445 to $4,090, compared with CrawlProof Autoblog at ${SCAN_CREDITS} credits (${dollars(SCAN_RACK_CENTS)} before volume discounts) per post.`}
>
<div className="flex items-start justify-between gap-4">
<div>
Expand All @@ -373,7 +373,7 @@ function AutoblogLaunchGraphic() {
</div>
<div className="rounded-lg border border-[rgba(110,231,183,0.35)] bg-[rgba(110,231,183,0.12)] px-3 py-2 text-right">
<div className="text-xs text-[var(--color-muted)]">Autoblog</div>
<div className="font-mono text-lg font-bold text-[var(--color-accent)]">~$1</div>
<div className="font-mono text-lg font-bold text-[var(--color-accent)]">{dollars(SCAN_RACK_CENTS)}</div>
</div>
</div>

Expand Down Expand Up @@ -403,7 +403,7 @@ function AutoblogLaunchGraphic() {
</div>
<div className="rounded-lg bg-[#17202a] p-3">
<div className="text-xs text-[var(--color-muted)]">CrawlProof</div>
<div className="font-mono text-lg font-bold text-[var(--color-accent)]">~$1</div>
<div className="font-mono text-lg font-bold text-[var(--color-accent)]">{dollars(SCAN_RACK_CENTS)}</div>
</div>
</div>

Expand Down
8 changes: 5 additions & 3 deletions app/(marketing)/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
LEAD_RUN_CREDITS,
OUTREACH_CREDITS,
SCAN_CREDITS,
SCAN_RACK_CENTS,
SCAN_MIN_CENTS,
SIGNUP_CREDITS,
discountPct,
dollars,
Expand All @@ -16,12 +18,12 @@ import {
export const metadata = {
title: "Pricing",
description:
"Rule-based AEO scans are free up to the daily limit. AI-model scans cost 20 credits (~$1) each, with volume discounts up to 50% off.",
`Rule-based AEO scans are free up to the daily limit. AI-model scans cost ${SCAN_CREDITS} credits (${dollars(SCAN_RACK_CENTS)}) each, with volume discounts down to ${dollars(SCAN_MIN_CENTS)} per scan.`,
alternates: { canonical: "/pricing" },
openGraph: {
title: "Pricing · CrawlProof",
description:
"Rule-based scans are free up to the daily limit. AI-model scans cost 20 credits (~$1) each, volume discounts to 50%.",
`Rule-based scans are free up to the daily limit. AI-model scans cost ${SCAN_CREDITS} credits (${dollars(SCAN_RACK_CENTS)}) each, with volume discounts down to ${dollars(SCAN_MIN_CENTS)} per scan.`,
url: "/pricing",
},
};
Expand All @@ -32,7 +34,7 @@ export default function PricingPage() {
<h1 className="text-center text-4xl font-extrabold">Pay per scan</h1>
<p className="mx-auto mt-2 max-w-2xl text-center text-[var(--color-muted)]">
Rule-based scans are free up to the daily limit. AI-model scans cost{" "}
<strong>{SCAN_CREDITS} credits (~$1) each</strong>. Buy credits with crypto via CoinPay;
<strong>{SCAN_CREDITS} credits ({dollars(SCAN_RACK_CENTS)} before volume discounts) each</strong>. Buy credits with card or crypto via CoinPay;
no subscription, no expiry.
</p>
<div className="mx-auto mt-8 grid max-w-6xl gap-4 md:grid-cols-2 lg:grid-cols-3">
Expand Down
4 changes: 2 additions & 2 deletions app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SCAN_CREDITS, SIGNUP_CREDITS } from "@/lib/credits";
import { SCAN_CREDITS, SIGNUP_CREDITS, SCAN_RACK_CENTS, SCAN_MIN_CENTS, dollars } from "@/lib/credits";
import { env } from "@/lib/env";

const body = `# CrawlProof
Expand All @@ -7,7 +7,7 @@ const body = `# CrawlProof

## Product
- Free single-URL AEO audit, 10 per day per IP, no signup required.
- Signed-in users get ${SIGNUP_CREDITS} free credits (${SIGNUP_CREDITS / SCAN_CREDITS} AI-model scan); each AI-model scan costs ${SCAN_CREDITS} credits (~$1, volume discounts down to $0.50/scan at the 100-scan pack). No subscription — credits never expire.
- Signed-in users get ${SIGNUP_CREDITS} free credits (${SIGNUP_CREDITS / SCAN_CREDITS} AI-model scan); each AI-model scan costs ${SCAN_CREDITS} credits (${dollars(SCAN_RACK_CENTS)} before volume discounts, down to ${dollars(SCAN_MIN_CENTS)}/scan at the 100-scan pack). No subscription — credits never expire.
- Saved projects, scheduled re-audits, multi-engine LLM scans (Claude Sonnet 4.6, OpenAI GPT-5 Mini, Gemini 2.5 Pro, Perplexity Sonar Pro, Qwen Plus, Kimi v2.6, DeepSeek V4, Z.AI GLM-5.2), consolidated PDF reports, and diff view.
- Identifies as CrawlProofBot/1.0 (+https://crawlproof.com/bot).

Expand Down
4 changes: 2 additions & 2 deletions components/json-ld.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { env } from "@/lib/env";
import { ANON_DAILY_SCANS, CREDIT_PACKS, SIGNUP_CREDITS } from "@/lib/credits";
import { ANON_DAILY_SCANS, CREDIT_PACKS, SIGNUP_CREDITS, SCAN_CREDITS, dollars, perScanCents } from "@/lib/credits";

function Tag({ data }: { data: unknown }) {
return (
Expand Down Expand Up @@ -59,7 +59,7 @@ export function SoftwareApplicationJsonLd() {
name: p.label,
price: (p.amountCents / 100).toFixed(2),
priceCurrency: "USD",
description: `${p.credits} scan${p.credits === 1 ? "" : "s"} · $${(p.amountCents / p.credits / 100).toFixed(2)}/scan`,
description: `${p.credits} credits · ${p.credits / SCAN_CREDITS} scan${p.credits === SCAN_CREDITS ? "" : "s"} · ${dollars(perScanCents(p))}/scan`,
})),
],
}}
Expand Down
3 changes: 2 additions & 1 deletion components/report/apply-fix-button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { SCAN_CREDITS, SCAN_RACK_CENTS, dollars } from "@/lib/credits";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";

Expand Down Expand Up @@ -281,7 +282,7 @@ export function ApplyFixButton({
{findingTitle}
</p>
<p className="mt-2 text-xs text-[var(--color-muted)]">
Costs <strong>20 credits (~$1)</strong>. Claude reads the relevant
Costs <strong>{SCAN_CREDITS} credits ({dollars(SCAN_RACK_CENTS)} before volume discounts)</strong>. Claude reads the relevant
files, proposes a minimal patch, and opens a PR. We refund
the credits if the run fails.
</p>
Expand Down
43 changes: 43 additions & 0 deletions docs/pricing-400-percent-markup-2026-09-13.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Credit pricing: 400% markup on service estimates

New purchases use a minimum price of five times the documented service-cost estimates, before affiliate and payment fees. The existing 30% affiliate commission remains in force. This is an 80% margin on the estimated service cost at the floor, not an 80% net margin after all expenses.

## Catalog

| Pack | Credits | Price | Per scan | Per credit | Discount |
| --- | ---: | ---: | ---: | ---: | ---: |
| Starter | 20 | $6 | $6 | $0.30 | 0% |
| 10 scans | 200 | $54 | $5.40 | $0.27 | 10% |
| 50 scans | 1,000 | $210 | $4.20 | $0.21 | 30% |
| 100 scans | 2,000 | $300 | $3 | $0.15 | 50% |

`lib/pricing-policy.ts` holds the service estimates and markup. `lib/credits.ts` converts each action estimate into a per-credit floor, takes the largest floor, and sets a rack price that still meets it after the largest discount. Every catalog entry is calculated and guarded against falling below that floor.

## Cost basis and limits

| Action | Existing service estimate | Credit charge | Five-times estimate per credit |
| --- | ---: | ---: | ---: |
| AI scan | 26c, from the previous scan catalog estimate | 20 | 6.5c |
| Lead run | 4.9c, from the existing per-tick search/drafting/render estimate | 3 | 8.34c, rounding the action quote up |
| Outreach | 3c, from `docs/social-posting-prd.md` browser/proxy/CAPTCHA/compute estimate | 1 | 15c |

Outreach sets the 15c credit floor. A 50% maximum volume discount requires a 30c rack price. Pricing from the scan estimate alone would underprice the other documented services.

These are estimates, not enforced spending ceilings. Custom lead drafts, SMS destinations and segments, article images, GitHub fix loops, retries and x402 paid fetches have variable costs. Existing production AI usage records capture individual calls, not complete job costs. This catalog must not be presented as a guarantee of 400% markup on every actual job or on total operating costs. Update the estimates as complete job measurements become available.

Articles, guest posts and GitHub fixes still consume 20 credits. Signup grants, existing balances and action deductions are unchanged. New purchase prices cannot retroactively improve the margin on credits already sold.

## Checkout and accounting

Card and crypto invoices both read the catalog server-side. The invoice route records the quoted price and credit count before asking CoinPay to create a payment. Caller-supplied amounts cannot lower the quote. Pending and completed purchases retain their stored amounts and credit counts; completion, affiliate conversion and receipts continue using those records.

Ad budgets retain their established 5c-per-credit accounting denomination. This is a redemption value, separate from the price paid to buy credits. The SQL click and deposit-match functions, publisher payouts, historical ledger rows and existing campaign dollar limits remain unchanged. Raising the credit purchase price must not also raise the publisher payout rate and erase the intended markup. The first-deposit bonus remains ad-only.

The homepage, pricing page, billing page, GitHub fix controls, structured offers and llms.txt derive their dollar claims from the catalog. Structured offers distinguish credits from scan equivalents.

## Verification

- Cost-floor tests cover every catalog pack against every documented service estimate after discounts.
- Invoice tests cover all four packs for both card and crypto, including caller-supplied stale prices and invalid pack IDs.
- Rendered pricing, structured offers and llms.txt agree on the new prices.
- Existing ad-solvency tests retain a historical-purchase fixture and check every new pack.
19 changes: 10 additions & 9 deletions lib/ads/pricing.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
// Ad network pricing. Advertiser spend is denominated in the existing credit
// unit (1 credit = 5¢, see lib/credits.ts CREDIT_RACK_CENTS) so one currency
// governs the app. v1 is a flat CPC; auction pricing is a later phase.
// Ad budget accounting retains its established 5c redemption denomination.
// This is separate from the purchase price of credits in lib/credits.ts.
// Changing purchase prices must not revalue existing campaign budgets, ledger
// rows, publisher claims or deposit-match promises. Matches the ad SQL RPCs.

// Advertiser spend value per credit (rack). Matches CREDIT_RACK_CENTS.
// Advertiser budget value per credit; this is not its cash purchase price.
export const CREDIT_CENTS = 5;

// Publisher cash-out value per credit.
//
// Deliberately BELOW the cheapest credit pack (2.5c on the 100-scan tier), not
// equal to it. Setting it at the floor price left the deepest pack with a 1:1
// Deliberately below even the old cheapest credit pack (2.5c per credit), not
// tied to the current purchase price. Setting it at the floor price left the deepest pack with a 1:1
// spread, and a deposit match on top of that pushed cash in per credit under
// the payout rate — see 20260731120000_ad_solvency.sql. At 2.0c the publisher
// earns 1.4c/credit after the platform rate, which keeps a margin on every
// pack (72% at rack, 44% on the deepest) with room for the match.
// historical pack with room for the match; repricing new packs does not raise payouts.
export const CREDIT_FLOOR_CENTS = 2.0;

// Minimum real cash that must sit behind every credit granted, in cents. The
// deposit match is capped so a purchase can never dilute below this — a 25%
// margin over the 1.4c publisher payout rate. Mirrors ad_apply_deposit_bonus().
export const MIN_CASH_PER_CREDIT_CENTS = 1.75;

// Default bid / cost-per-click, in credits. 4 credits = $0.20 at rack.
// Default bid / cost-per-click, in credits. 4 credits uses $0.20 of ad budget.
export const CPC_CREDITS = 4;
export const DEFAULT_BID_CREDITS = CPC_CREDITS;
export const CPC_CENTS = CPC_CREDITS * CREDIT_CENTS;
Expand Down Expand Up @@ -52,7 +53,7 @@ export const MIN_PAYOUT_CENTS = 500; // $5.00

// Deposit-match promo: the first deposit is matched 100% of the credits
// BOUGHT (not of the dollar amount at rack — that over-granted on discounted
// packs), capped at $100 of rack value and further capped so the deposit never
// packs), capped at $100 of ad budget value and further capped so the deposit never
// dilutes below MIN_CASH_PER_CREDIT_CENTS. ad_apply_deposit_bonus() in
// 20260731120000_ad_solvency.sql is the source of truth.
export const DEPOSIT_MATCH_RATE = 1.0;
Expand Down
Loading
Loading