master-zapier-plan-draft/research/zapier/scripts/generate-zapier-billing-pdf.py
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

758 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Render docs/zapier-billing.pdf — Zapier cost structure for Verae planning.
Platypus Paragraphs for bullets (never ListFlowable). Every table cell is a Paragraph.
Avoid '|' in body text (Helvetica renders it like a capital I).
"""
from __future__ import annotations
from pathlib import Path
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import (
PageBreak,
Paragraph,
Preformatted,
SimpleDocTemplate,
Spacer,
Table,
TableStyle,
)
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "docs" / "zapier-billing.pdf"
NAVY = colors.HexColor("#0F2744")
TEAL = colors.HexColor("#1A6B6B")
SLATE = colors.HexColor("#334155")
RULE = colors.HexColor("#CBD5E1")
ROW = colors.HexColor("#F1F5F9")
HEAD_BG = colors.HexColor("#0F2744")
HEAD_FG = colors.white
CODE_BG = colors.HexColor("#F8FAFC")
def styles():
base = getSampleStyleSheet()
return {
"cover_kicker": ParagraphStyle(
"cover_kicker", parent=base["Normal"], fontName="Helvetica",
fontSize=10, textColor=TEAL, alignment=TA_CENTER, spaceAfter=10,
),
"cover_title": ParagraphStyle(
"cover_title", parent=base["Title"], fontName="Helvetica-Bold",
fontSize=24, leading=30, textColor=NAVY, alignment=TA_CENTER, spaceAfter=10,
),
"cover_sub": ParagraphStyle(
"cover_sub", parent=base["Normal"], fontName="Helvetica",
fontSize=11.5, leading=16, textColor=SLATE, alignment=TA_CENTER, spaceAfter=8,
),
"h1": ParagraphStyle(
"h1", parent=base["Heading1"], fontName="Helvetica-Bold",
fontSize=14.5, leading=18, textColor=NAVY, spaceBefore=14, spaceAfter=7,
),
"h2": ParagraphStyle(
"h2", parent=base["Heading2"], fontName="Helvetica-Bold",
fontSize=11.5, leading=15, textColor=TEAL, spaceBefore=10, spaceAfter=5,
),
"body": ParagraphStyle(
"body", parent=base["Normal"], fontName="Helvetica",
fontSize=9.4, leading=13, textColor=SLATE, alignment=TA_LEFT, spaceAfter=6,
),
"bullet": ParagraphStyle(
"bullet", parent=base["Normal"], fontName="Helvetica",
fontSize=9.4, leading=12.8, textColor=SLATE, leftIndent=14,
firstLineIndent=-10, spaceAfter=3,
),
"toc": ParagraphStyle(
"toc", parent=base["Normal"], fontName="Helvetica",
fontSize=10, leading=15, textColor=NAVY, leftIndent=6, spaceAfter=2,
),
"cell": ParagraphStyle(
"cell", parent=base["Normal"], fontName="Helvetica",
fontSize=7.8, leading=10.4, textColor=SLATE,
),
"cell_h": ParagraphStyle(
"cell_h", parent=base["Normal"], fontName="Helvetica-Bold",
fontSize=7.8, leading=10.4, textColor=HEAD_FG,
),
"code": ParagraphStyle(
"code", parent=base["Code"], fontName="Courier",
fontSize=7.6, leading=10.2, textColor=NAVY, backColor=CODE_BG,
leftIndent=4, rightIndent=4, spaceBefore=3, spaceAfter=7,
),
"caption": ParagraphStyle(
"caption", parent=base["Normal"], fontName="Helvetica-Oblique",
fontSize=8, leading=11, textColor=colors.HexColor("#64748B"), spaceAfter=8,
),
}
S = styles()
USABLE = letter[0] - 1.4 * inch
def P(text, style="body"):
return Paragraph(text, S[style])
def B(text):
return Paragraph(f"{text}", S["bullet"])
def H1(text):
return Paragraph(text, S["h1"])
def H2(text):
return Paragraph(text, S["h2"])
def CODE(text):
return Preformatted(text.rstrip() + "\n", S["code"])
def tbl(headers, rows, widths=None):
if widths is None:
widths = [USABLE / len(headers)] * len(headers)
data = [[Paragraph(h, S["cell_h"]) for h in headers]]
for row in rows:
data.append([Paragraph(c, S["cell"]) for c in row])
t = Table(data, colWidths=widths, repeatRows=1)
cmds = [
("BACKGROUND", (0, 0), (-1, 0), HEAD_BG),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LEFTPADDING", (0, 0), (-1, -1), 4),
("RIGHTPADDING", (0, 0), (-1, -1), 4),
("TOPPADDING", (0, 0), (-1, -1), 3.5),
("BOTTOMPADDING", (0, 0), (-1, -1), 3.5),
("GRID", (0, 0), (-1, -1), 0.3, RULE),
("LINEBELOW", (0, 0), (-1, 0), 1, TEAL),
]
for i in range(1, len(data)):
if i % 2 == 0:
cmds.append(("BACKGROUND", (0, i), (-1, i), ROW))
t.setStyle(TableStyle(cmds))
return t
def header_footer(canvas, doc):
canvas.saveState()
w, h = letter
canvas.setFillColor(NAVY)
canvas.rect(0, h - 28, w, 28, fill=1, stroke=0)
canvas.setFillColor(colors.white)
canvas.setFont("Helvetica", 8)
canvas.drawString(0.7 * inch, h - 18, "Verae Time · Zapier billing brief")
canvas.drawRightString(w - 0.7 * inch, h - 18, "Cost structure and variable pricing")
canvas.setFillColor(TEAL)
canvas.rect(0, 0, w, 22, fill=1, stroke=0)
canvas.setFillColor(colors.white)
canvas.setFont("Helvetica", 8)
canvas.drawString(0.7 * inch, 8, "Internal planning · 18 August 2026 · USD list from zapier.com/pricing")
canvas.drawRightString(w - 0.7 * inch, 8, f"{doc.page}")
canvas.restoreState()
def cover_footer(canvas, doc):
canvas.saveState()
w, _ = letter
canvas.setFillColor(NAVY)
canvas.rect(0, 0, w, 56, fill=1, stroke=0)
canvas.setFillColor(TEAL)
canvas.rect(0, 56, w, 4, fill=1, stroke=0)
canvas.setFillColor(colors.white)
canvas.setFont("Helvetica", 9)
canvas.drawCentredString(w / 2, 28, "Confirm live rates before any customer quote")
canvas.setFont("Helvetica", 8)
canvas.drawCentredString(w / 2, 14, "18 August 2026")
canvas.restoreState()
def story():
out = []
out.append(Spacer(1, 1.5 * inch))
out.append(P("VERAE TIME · COMMERCIAL PLANNING", "cover_kicker"))
out.append(P("Zapier cost structure<br/>and billing models", "cover_title"))
out.append(
P(
"How Zapier charges for Zaps, MCP, AI, Code, Agents, and embed — "
"with worked examples — so we can design Veraes meter without "
"subsidizing or colliding with Zapier.",
"cover_sub",
)
)
out.append(Spacer(1, 14))
out.append(
tbl(
["Source", "As of", "Use"],
[
[
"zapier.com/pricing (official machine-readable page)",
"1718 Aug 2026",
"Plan levels, task tiers, overflow, add-ons",
],
[
"Zapier “What is a task?” (updated June 2026)",
"June 2026",
"What counts; AI 1/3/5; MCP = 2",
],
[
"verae-zapier-middleware PLAN_LIMITS",
"This repo",
"Our second meter (timestamps / verify / batch)",
],
],
[2.5 * inch, 1.5 * inch, 2.5 * inch],
)
)
out.append(PageBreak())
out.append(H1("Contents"))
for line in [
"1. The one idea that unlocks the rest",
"2. What a task is (and is not)",
"3. Plan levels — what the customer can build",
"4. Variable pricing inside the task pool",
"5. Task tiers and list price",
"6. Overflow (pay-per-task)",
"7. Other Zapier products — different models",
"8. Worked examples",
"9. Veraes meter today",
"10. How this should shape our billing",
"11. Planning checklist",
]:
out.append(P(line, "toc"))
out.append(
P(
"Companion narrative: docs/zapier-billing.md. Architecture guide: getting-started.md §910.",
"caption",
)
)
# 1
out.append(H1("1. The one idea that unlocks the rest"))
out.append(
P(
"Zapier does not charge per Zap, per connected app, or (on Professional) per seat. "
"It charges for <b>successful work</b>. You buy two things that travel together: "
"a <b>plan level</b> (feature set) and a <b>task tier</b> (monthly allowance). "
"Enterprise swaps the monthly reset for an annual task pool."
)
)
out.append(
P(
"Three other economies sit <b>beside</b> that pool, not inside it:"
)
)
out.append(
tbl(
["Economy", "Unit", "Used for"],
[
["Core Zapier", "Task", "Zaps, AI by Zapier, Code by Zapier, MCP, SDK"],
["Agents add-on", "Activity", "Zapier Agents — does not draw tasks"],
["Chatbots add-on", "Bot-count tier", "Zapier Chatbots — not task-metered"],
["Verae (us)", "Timestamp / verify / batch", "Middleware PLAN_LIMITS — a second invoice"],
],
[1.7 * inch, 1.8 * inch, 3.0 * inch],
)
)
out.append(
P(
"Verae never appears on the Zapier invoice unless we sign a White Label / "
"Powered by Zapier contract and resell Zapier usage ourselves.",
"caption",
)
)
# 2
out.append(H1("2. What a task is (and is not)"))
out.append(
P(
"A task is counted when Zapier <b>successfully completes</b> a unit of work. "
"Failed steps are free on Zapier (they can still cost us if we already accepted the job)."
)
)
out.append(
tbl(
["Counts as tasks", "Does not count"],
[
[
"Successful action in another product (Create Timestamp, Slack, Drive, Sheets, webhook)",
"The trigger — including polling every 115 minutes",
],
[
"Successful Zapier MCP tool execute (read or write)",
"Filter, Paths, Formatter, Delay, Looping, Sub-Zap, Digest, Manager, Storage",
],
[
"AI by Zapier and Code by Zapier (see multipliers / extra runtime)",
"Zapier Tables and Forms triggers and actions",
],
[
"SDK execute once beta pricing starts (today: beta is free)",
"Building or testing a Zap until a step succeeds in production",
],
],
[3.25 * inch, 3.25 * inch],
)
)
out.append(
P(
"One shared allowance for the whole account. There is no separate MCP budget. "
"A five-step Zap that is trigger + Filter + Formatter + Timestamp + Slack uses "
"<b>two</b> tasks, not five."
)
)
# 3
out.append(H1("3. Plan levels — what the customer can build"))
out.append(P("Plans are cumulative. Task <b>volume</b> is chosen separately (section 5)."))
out.append(
tbl(
["", "Free", "Professional", "Team", "Enterprise"],
[
["Seats", "1", "1", "25", "Unlimited"],
["Zap shape", "Two-step only", "Multi-step", "Multi-step", "Multi-step"],
["Polling", "15 min", "2 min", "1 min", "1 min"],
["Premium apps, webhooks", "No", "Yes", "Yes", "Yes"],
["Filters, Paths, Formatter, AI", "No", "Yes", "Yes", "Yes"],
["Shared Zaps, SAML SSO", "No", "No", "Yes", "Yes"],
["SCIM, app controls, TAM, BYOM", "No", "No", "No", "Yes"],
["Task cycle", "Monthly", "Monthly", "Monthly", "Annual pool"],
["Pay-per-task overflow", "No", "Optional", "Optional", "Custom"],
[
"Entry price (annual, USD/mo)",
"$0 (100 tasks)",
"$19.99 (750 tasks)",
"$69 (2,000 tasks)",
"Sales",
],
],
[1.55 * inch, 1.15 * inch, 1.35 * inch, 1.25 * inch, 1.2 * inch],
)
)
out.append(
P(
"14-day Professional trial, no card. Non-profit: extra 15% off the subscription, "
"not on pay-per-task. Live chat on Professional only at the 2,000+ task tier.",
"caption",
)
)
out.append(
P(
"<b>Verae implication:</b> a Free customer can only run “new file → Create Timestamp.” "
"A catalog write, Slack notify, or Wait plus another action requires Professional."
)
)
# 4
out.append(H1("4. Variable pricing inside the task pool"))
out.append(
P(
"Not every successful step costs one task. Zapier uses multipliers so expensive "
"compute consumes more of the <b>same</b> allowance. Confirm /pricing/rates before "
"a contract; the page was not fetchable at write time. These multipliers are what "
"Zapier publishes on the main pricing page and the June 2026 task article."
)
)
out.append(
tbl(
["Work", "Tasks per success", "Notes"],
[
["Typical third-party action (Verae Create Timestamp, Slack, Drive, Sheets)", "1", "The default. Design around this."],
["Standard AI by Zapier (default model)", "1", "Same as a normal action"],
["Advanced AI by Zapier", "3", "Confirm on the rate card"],
["Premium AI by Zapier", "5", "Confirm on the rate card"],
["Zapier MCP execute (read or write)", "2", "Discover / inspect / enable are free meta-tools"],
[
"Code by Zapier",
"0, then 1 per extra 30 s",
"Included: Free 1s, Pro/Team 30s, Enterprise 2 min. Extended runtime opt-in 18 min on paid.",
],
["Zapier SDK", "Free in beta", "Expect it to join the task pool when beta ends"],
],
[2.5 * inch, 1.5 * inch, 2.5 * inch],
)
)
out.append(
P(
"Failed steps = 0 Zapier tasks. Autoreplay and customer retries can still hit Verae. "
"That is our problem, not Zapiers."
)
)
# 5
out.append(H1("5. Task tiers and list price (USD, August 2026)"))
out.append(
P(
"Self-serve is sold as plan × tier. Annual is about 33% off monthly. "
"Implied dollars per task = list price ÷ included tasks (order-of-magnitude only)."
)
)
out.append(H2("Professional"))
out.append(
tbl(
["Tasks / mo", "Annual / mo", "Monthly / mo", "Implied $/task (annual)"],
[
["750", "$19.99", "$29.99", "$0.027"],
["1,500", "$39.00", "$58.50", "$0.026"],
["2,000", "$49.00", "$73.50", "$0.025"],
["5,000", "$89.00", "$133.50", "$0.018"],
["10,000", "$129.00", "$193.50", "$0.013"],
["20,000", "$189.00", "$283.50", "$0.0095"],
["50,000", "$289.00", "$433.50", "$0.0058"],
["100,000", "$489.00", "$733.50", "$0.0049"],
["200,000", "$769.00", "$1,149.00", "$0.0038"],
["500,000", "$1,499.00", "$2,199.00", "$0.0030"],
["1,000,000", "$2,199.00", "$3,299.00", "$0.0022"],
["2,000,000", "$3,389.00", "$5,099.00", "$0.0017"],
],
[1.6 * inch, 1.6 * inch, 1.6 * inch, 1.7 * inch],
)
)
out.append(H2("Team (starts at 2,000)"))
out.append(
tbl(
["Tasks / mo", "Annual / mo", "Monthly / mo", "Implied $/task (annual)"],
[
["2,000", "$69.00", "$103.50", "$0.035"],
["5,000", "$119.00", "$178.50", "$0.024"],
["10,000", "$169.00", "$253.50", "$0.017"],
["20,000", "$249.00", "$373.50", "$0.012"],
["50,000", "$399.00", "$598.50", "$0.008"],
["100,000", "$599.00", "$898.50", "$0.006"],
["1,000,000", "$2,499.00", "$3,749.00", "$0.0025"],
["2,000,000", "$3,999.00", "$5,999.00", "$0.0020"],
],
[1.6 * inch, 1.6 * inch, 1.6 * inch, 1.7 * inch],
)
)
out.append(
P(
"Teams entry dollars-per-task is <b>higher</b> than Pro because the customer is "
"buying seats, shared connections, and SSO — not cheaper tasks. Intermediate "
"tiers (300k, 400k, 750k, 1.25M, 1.5M, 1.75M) exist on the live page. Above 2M: Sales.",
"caption",
)
)
# 6
out.append(H1("6. Overflow (pay-per-task)"))
out.append(
tbl(
["Setting", "What happens"],
[
[
"On (paid plans)",
"Zaps and MCP keep running. Extra tasks bill at 1.25× the plans base dollars-per-task (annual) or 2.5× (monthly). Hard ceiling: 3× subscribed tasks, then pause.",
],
["Off", "Everything stops at the allowance."],
["Free", "No overflow. Hits 100 and stops."],
["Enterprise", "Annual pool instead of a monthly reset; overflow is contractual."],
],
[1.6 * inch, 4.9 * inch],
)
)
out.append(
P(
"Worked overage: Professional 750 annual → base ≈ $19.99 / 750 = <b>$0.0267</b>. "
"Overflow ≈ <b>$0.0333</b> per task. Same plan billed monthly: base ≈ $0.0400, "
"overflow ≈ <b>$0.100</b>. Overflow on a monthly subscription is about 3× the "
"annual overflow rate."
)
)
# 7
out.append(H1("7. Other Zapier products — different models"))
out.append(
tbl(
["Product", "Model", "Relation to tasks"],
[
["Zap workflows", "Task", "Core"],
["Zapier MCP", "Same pool; 2 tasks per successful execute", "Meta-tools free"],
["Zapier SDK", "Beta free; expect tasks later", "Consume, not publish"],
["AI by Zapier", "Task × model tier (1 / 3 / 5)", "Inside the Zap"],
["Code by Zapier", "Included seconds + 1 task / 30 s extra", "Inside the Zap"],
["Tables / Forms", "Plan caps (records, pages, upload size)", "0 tasks when used in Zaps"],
[
"Agents",
"Activities (Free 400/mo; paid ~$33.33/mo annual for 1,500). Per-run cap 10 / 40.",
"Does not use tasks",
],
["Chatbots", "Bot-count tiers (Free 2; paid ≈5 / ≈20)", "Does not use tasks"],
["Canvas / Copilot", "Included; Free Copilot has a daily message limit", "Not a usage meter"],
[
"Directory integration (us)",
"$0 to publish. Customers Zapier plan pays tasks.",
"Partner is not billed",
],
[
"White Label / Powered by Zapier / embed",
"Usage-based to the product company. End users may not need their own Zapier bill.",
"Sales contract",
],
["NLA / AI Actions", "Retired", "Do not design around this"],
],
[1.8 * inch, 2.8 * inch, 1.9 * inch],
)
)
# 8
out.append(H1("8. Worked examples"))
out.append(
P(
"Assume Professional billed <b>annually</b> unless noted, and that every named "
"action succeeds. Verae units are <b>our</b> meter."
)
)
out.append(H2("A. Two-step timestamp (Free-capable)"))
out.append(P("New file in Drive → Verae: Create Timestamp · 1 Zapier task and 1 Verae stamp per file."))
out.append(
tbl(
["Volume", "Zapier tasks", "Fits", "Zapier $ (annual)", "Verae"],
[
["80 files / mo", "80", "Free", "$0", "80 stamps — over our free (50)"],
["200", "200", "Pro 750", "$19.99", "200 — still under our starter (500)"],
["600", "600", "Pro 750", "$19.99", "600 — over our starter; we 402 first"],
],
[1.3 * inch, 1.2 * inch, 1.1 * inch, 1.4 * inch, 1.5 * inch],
)
)
out.append(
P(
"Lesson: the customer can be fine on Zapier and still hit <b>our</b> 402. "
"The two meters trip at different points."
)
)
out.append(H2("B. Production pattern we recommend"))
out.append(
P(
"New file → Filter → Create Timestamp → Formatter → write catalog "
"(Zapier Tables = <b>0</b> tasks) → Slack. Billable: Timestamp + Slack = "
"<b>2 tasks per file that passes the filter</b>."
)
)
out.append(
tbl(
["Files / mo", "Pass filter", "Zapier tasks", "Cheapest Pro tier", "Zapier $", "Verae stamps"],
[
["1,000", "20% (200)", "400", "750", "$19.99", "200"],
["1,000", "100%", "2,000", "2,000", "$49", "1,000 (our pro)"],
["10,000", "100%", "20,000", "20,000", "$189", "10,000 (contract)"],
],
[1.05 * inch, 1.1 * inch, 1.05 * inch, 1.25 * inch, 0.95 * inch, 1.1 * inch],
)
)
out.append(
P(
"Filter early: 800 of 1,000 files dropped costs 0 Zapier tasks and 0 Verae stamps. "
"That is the single best cost control we can teach customers."
)
)
out.append(H2("C. Create and Wait versus hook"))
out.append(B("Wait in the Zap: 1 Zapier task. Fine when the user needs the certificate in the same run."))
out.append(
B(
"Async create + Timestamp Completed hook + update row: 1 task to create + 1 task "
"when the hook fires an action = 2 tasks, but the Zap does not sit open. Prefer at volume."
)
)
out.append(
B(
"Anti-pattern: polling Find Job Status every minute. Each successful search is a task."
)
)
out.append(H2("D. Same work via Zapier MCP (an agent)"))
out.append(
P(
"Agent executes Create Timestamp, write Sheet, Slack. Three executes × 2 tasks = "
"<b>6 Zapier tasks per file</b>, plus Agent activities if they used Zapier Agents."
)
)
out.append(
tbl(
["Files / mo", "Zapier tasks", "Cheapest Pro", "Zapier $", "Same stamps via Example B"],
[
["200", "1,200", "1,500 ($39)", "$39", "400 tasks / $19.99"],
["1,000", "6,000", "10,000 ($129)", "$129", "2,000 tasks / $49"],
],
[1.2 * inch, 1.2 * inch, 1.5 * inch, 1.1 * inch, 1.5 * inch],
)
)
out.append(
P(
"<b>Planning rule:</b> MCP-shaped clients burn Zapier 2× per hop. Do not price "
"Verae as if the customers only cost is our stamp."
)
)
out.append(H2("E. AI enrichment in the Zap"))
out.append(
P(
"New file → Standard AI extract (1) → Create Timestamp (1) → Sheet (1) = "
"<b>3 tasks</b>. Swap Premium AI: 5+1+1 = <b>7 tasks</b>. "
"1,000 files: 3,000 vs 7,000 tasks → $89 vs $129 on Pro annual. "
"Model choice is the customers Zapier bill unless we bury AI inside our action "
"(we should not)."
)
)
out.append(H2("F. Overflow month"))
out.append(
P(
"Customer on Pro 750 annual ($19.99). A campaign pushes 1,400 successful "
"timestamp-only runs."
)
)
out.append(CODE(
"Included: 750\n"
"Overflow: 650 × ~$0.0333 ≈ $22\n"
"Total Zapier that month ≈ $42\n"
"Still under the 3× ceiling (2,250)\n"
"\n"
"Same usage on monthly Pro 750 ($29.99):\n"
"Overflow: 650 × ~$0.100 ≈ $65\n"
"Plus subscription ≈ $95"
))
out.append(H2("G. White Label — we are the billed party"))
out.append(
P(
"If Verae embeds Zapier and Zapier bills <b>us</b> per task, then 10,000 "
"customer files × 2 tasks = 20,000 tasks ≈ <b>$189/mo</b> at Pro annual list "
"<b>plus</b> our timestamp COGS. That number belongs in our COGS, not in the "
"end customers Zapier account."
)
)
# 9
out.append(H1("9. Veraes meter today"))
out.append(P("From verae-zapier-middleware PLAN_LIMITS — independent of Zapier."))
out.append(
tbl(
["Verae plan", "Timestamps / mo", "Verifications", "Batch", "RPM"],
[
["free", "50", "50", "no", "30"],
["starter", "500", "500", "yes, max 10", "120"],
["pro", "5,000", "5,000", "yes, max 100", "600"],
["enterprise", "unlimited / contract", "contract", "yes", "3,000"],
],
[1.4 * inch, 1.5 * inch, 1.3 * inch, 1.2 * inch, 1.1 * inch],
)
)
out.append(
P(
"Over-quota → HTTP 402 QUOTA_EXCEEDED. Batch on free → 403 PLAN_UPGRADE_REQUIRED. "
"These fire whether or not Zapier is still inside its task allowance."
)
)
# 10
out.append(H1("10. How this should shape our billing"))
out.append(B(
"<b>1. Never bundle “unlimited Zapier.”</b> We do not control their task tier, "
"MCP multiplier, or overflow toggle."
))
out.append(B(
"<b>2. Meter what we uniquely do:</b> accepted timestamp jobs, verifies, maybe "
"stored GB / pin-days / Glacier restores — not Zap steps."
))
out.append(B(
"<b>3. Mirror Zapiers shape if we want familiarity:</b> plan + included units + "
"optional overage with a ceiling. Customers already understand 402 versus pause."
))
out.append(B(
"<b>4. Do not copy Zapiers 2× MCP tax onto our API.</b> POST /zapier/v1/timestamp "
"should stay one Verae unit whether the Zap used 1 task or an agent used 2."
))
out.append(B(
"<b>5. Price batch as a plan gate.</b> Zapier still charges 1 task for the batch "
"action. We decide whether 100 items cost 1 or 100 of our units. Today we count "
"batch size against batchMaxItems and timestamp quota."
))
out.append(B(
"<b>6. Package for the hook pattern.</b> Include enough monthly stamps that "
"create + hook + catalog is cheaper on our side than polling status."
))
out.append(B(
"<b>7. Three motions, three packages.</b> Directory user = they pay Zapier + they "
"pay Verae. MCP/agent user = they already pay 2 Zapier tasks per hop; keep us "
"simple. Embed/White Label = Zapier may bill us; either cover that COGS or sell "
"only the stamp and let them bring their own Zapier."
))
out.append(B(
"<b>8. Storage / Peergos / Glacier is a third meter</b> (getting-started §10). "
"Do not hide pin-days or restore fees inside “one timestamp.”"
))
out.append(B(
"<b>9. Bill on accepted jobId (HTTP 202),</b> not on Zapiers success callback. "
"A failed later Zap step is free for them and already spent for us."
))
out.append(B(
"<b>10. Quote two lines</b> in every proposal: “Your Zapier plan (estimate N tasks)” "
"and “Verae (M timestamps).” Never a single blended number."
))
out.append(Spacer(1, 6))
out.append(
P(
"At Pro 750 annual, Zapiers included work is about <b>2.7 cents per task</b>. "
"If Create Timestamp is one Zapier task + one Verae stamp, the customers "
"Zapier share of a two-step Zap is ~$0.027. Price our stamp from chain/ops "
"cost, not 1:1 to that 2.7¢ — but if we charge dollars per stamp while Zapier "
"is cents per step, SMB Zaps will feel Verae-expensive even when Zapier is "
"the bigger invoice at volume."
)
)
# 11
out.append(H1("11. Planning checklist"))
out.append(B("For each target Zap, count billable Zapier <b>actions</b>, not steps on the canvas."))
out.append(B("Multiply MCP executes by 2."))
out.append(B("Apply AI 1 / 3 / 5 if they enrich in the Zap."))
out.append(B("Pick the cheapest Zapier tier that covers that task count (or run overflow math)."))
out.append(B("Count Verae timestamps / verifies / batch separately; check the 50 / 500 / 5,000 tripwires."))
out.append(B("If we embed Zapier, put Zapier list price into our COGS."))
out.append(B("Keep Peergos pin / Glacier restore off the timestamp SKU."))
out.append(B("Re-check zapier.com/pricing before any contract. This brief is dated 18 August 2026."))
out.append(Spacer(1, 14))
out.append(
P(
"Related: docs/zapier-billing.md · getting-started.md §910 · "
"docs/diagrams/11-zapier-billing.svg · LOGIN.md (customer must have a Zapier plan to run Zaps).",
"caption",
)
)
return out
def main():
OUT.parent.mkdir(parents=True, exist_ok=True)
doc = SimpleDocTemplate(
str(OUT),
pagesize=letter,
leftMargin=0.7 * inch,
rightMargin=0.7 * inch,
topMargin=0.55 * inch,
bottomMargin=0.45 * inch,
title="Zapier cost structure and billing models — Verae planning brief",
author="Verae / Zapier research workspace",
subject="Official Zapier pricing (Aug 2026) with examples for Verae billing design",
)
doc.build(story(), onFirstPage=cover_footer, onLaterPages=header_footer)
print(f"wrote {OUT} ({OUT.stat().st_size} bytes)")
if __name__ == "__main__":
main()