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.
758 lines
30 KiB
Python
758 lines
30 KiB
Python
#!/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 Verae’s 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)",
|
||
"17–18 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. Verae’s 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 §9–10.",
|
||
"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 1–15 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 1–8 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 Zapier’s."
|
||
)
|
||
)
|
||
|
||
# 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(
|
||
"Team’s 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 plan’s 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. Customer’s 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 customer’s 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 customer’s 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 customer’s Zapier account."
|
||
)
|
||
)
|
||
|
||
# 9
|
||
out.append(H1("9. Verae’s 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 Zapier’s 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 Zapier’s 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 Zapier’s 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, Zapier’s included work is about <b>2.7 cents per task</b>. "
|
||
"If Create Timestamp is one Zapier task + one Verae stamp, the customer’s "
|
||
"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 §9–10 · "
|
||
"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()
|