Pricing Strategy skill for Codex
Price on the value metric, backed by WTP data. Codex works through pricing in dependency order: segments, value metric, model, packaging, price points, then rollout. It analyzes Van Westendorp survey data with a bundled script that drops inconsistent answers and reports the acceptable range per segment, checks prices against cost floors and customer alternatives, and treats changes for existing customers as a contractual step needing approval and legal review.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- pricing-strategy Installs to ~/.agents/skills/pricing-strategy/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install pricing-strategy The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project pricing-strategy Installs to .agents/skills/pricing-strategy/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/pricing-strategy.zipunzip -o pricing-strategy.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) pricing-strategy PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $pricing-strategy, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Works through SaaS and digital product pricing decisions - choosing a value metric, pricing model (flat, per seat, usage-based, tiered, hybrid), good-better-best packaging, and price points - backed by willingness-to-pay research (Van Westendorp price sensitivity survey with a bundled analysis script, Gabor-Granger, conjoint), competitor and cost floors, and a safe rollout plan for price changes with grandfathering. Use when asked to price a new product, restructure plans or tiers, design a pricing survey, analyze willingness-to-pay data, or plan a price increase.
Use it when
- You are pricing a new product or plan
- Tiers have grown confusing and need repackaging
- You have or plan a willingness-to-pay survey
- A price increase for existing customers is being considered
Use something else when
- You are designing the pricing page layout and copy: use pricing-page
- You need to implement subscriptions and invoices in Stripe: use stripe-billing
- You need revenue, churn and LTV calculations: use saas-metrics
How to check the result
Script output matches the reported price points, chosen prices sit inside the measured acceptable range and above the cost floor, and each tier names its target segment.
The mistake it prevents
Copying a competitor's price list, which imports their segment and cost structure while ignoring what your own customers value.
A pricing decision is done well when the price scales with the value customers receive (the value metric), each tier exists for a named segment, price points rest on willingness-to-pay evidence rather than competitor copying, and existing customers are handled explicitly before anything changes. Pricing affects revenue, contracts and consumer-law obligations: final decisions belong to the business owner, and price-change notices should be reviewed by legal or finance.
Order of decisions
Decide in this order; later choices depend on earlier ones.
1. Segments: who buys, and how their value differs
2. Value metric: the unit price scales with
3. Model: how the metric turns into a bill
4. Packaging: which features and limits go in which tier
5. Price points: the numbers
6. Rollout: new vs existing customers, discounts, communication
Value metric
A good value metric grows as the customer gets more value, is easy for the buyer to predict, and is easy to measure and bill.
| Candidate | Fits when | Risk |
|---|---|---|
| Seats / users | Value grows with people collaborating | Discourages adding users; seat sharing |
| Active users | Adoption varies a lot by customer | Bills are harder to predict |
| Usage (API calls, GB, minutes, messages) | Cost and value scale with volume | Bill shock; needs spend caps and alerts |
| Outcomes (transactions processed, revenue share) | Value is directly measurable | Hard to forecast; disputes |
| Entities (projects, contacts, locations) | Value tied to scale of what is managed | Customers game by consolidating |
| Flat fee | Single segment, simple product | Leaves money on the table with large customers |
Test each candidate: “If a customer gets twice the value, does this number roughly double?” and “Could the buyer predict next month’s bill?”
Model and packaging
- Good-better-best (three tiers) is the default for self-serve SaaS. Design each tier for a named segment, put the features that segment cannot do without in its tier, and gate on things large customers need (SSO, audit logs, admin controls, higher limits), not on core value everyone needs.
- Hybrid (platform fee plus usage) when there is a fixed cost to serve plus variable consumption.
- Free tier vs free trial: free tier when network effects or bottom-up adoption drive growth and marginal cost is low; time-limited trial when value appears within days and serving free users is expensive.
- Keep the number of plans small enough to compare at a glance; add an “Enterprise - contact us” tier rather than a fourth self-serve tier.
Pricing page layout and copy belong to $pricing-page; Stripe implementation details to
$stripe-billing.
Willingness-to-pay research
| Method | Use when | Sample | Output |
|---|---|---|---|
| Qualitative pricing interviews | Early, defining value and metric | 10-20 | Value drivers, budget owners, anchors |
| Van Westendorp PSM | Need an acceptable range for a defined offer | 100+ per segment (30 minimum) | Range and indicative points |
| Gabor-Granger | Choosing among specific price points | 100+ per segment | Demand curve, revenue-maximizing point |
| Conjoint (choice-based) | Trading off features vs price across packages | 200+; specialist tooling | Part-worths, feature value |
| Live price test | Self-serve traffic is large enough | Depends on conversion rate | Real conversion and revenue per visitor |
Stated willingness to pay overestimates real behaviour; treat survey output as a range to test, not a final answer.
Van Westendorp survey
Show the offer description first (what it includes, billing period, currency), then ask in this order:
- At what price would this be so cheap that you would question its quality? (too cheap)
- At what price would it be a bargain, a great buy for the money? (cheap)
- At what price would it start to feel expensive, but you would still consider it? (expensive)
- At what price would it be too expensive to consider? (too expensive)
Analyze with the bundled script (drops respondents whose answers are out of order, needs at least 30 valid rows):
python3 scripts/van_westendorp.py responses.csv
python3 scripts/van_westendorp.py responses.csv --segment-col company_size --segment 50-200
CSV columns: too_cheap,cheap,expensive,too_expensive plus optional segment columns. It
prints the point of marginal cheapness (PMC), optimal price point (OPP), indifference
price point (IPP), point of marginal expensiveness (PME) and the acceptable range PMC to
PME. Run it per segment; pooled results hide segments with very different budgets.
Curve-crossing conventions differ slightly between tools; report which one you used.
Survey sampling and question bias: $survey-design.
Floors and anchors
- Cost floor: gross margin target (often 70-80%+ for SaaS) on cost to serve, including infrastructure, third-party API costs and support. Usage-heavy AI features need per-unit cost modelling before any flat price.
- Alternatives: what the customer pays today (competitor, spreadsheet plus staff time, agency). Competitor prices are anchors, not answers.
- Value ceiling: quantified value for the customer (hours saved x loaded cost, revenue gained). Price usually captures 10-30% of quantified value.
Price changes for existing customers
Changing what existing customers pay is a high-trust, often contractual action.
- [ ] Inventory affected customers by plan, contract terms and renewal dates
- [ ] Choose: grandfather indefinitely, grandfather for N months, or migrate at renewal
- [ ] Model revenue and churn impact for each option (show assumptions)
- [ ] Check contract terms and notice obligations with legal/finance
- [ ] Draft customer communication: what changes, when, why, what they keep
- [ ] Get explicit approval before any email is sent or billing is changed
- [ ] Monitor churn, downgrades and support tickets for 60-90 days
Never change prices in a billing system or send pricing emails from Codex without the user’s explicit confirmation of the exact change and audience.
Output template
## Pricing recommendation: <product>
Segments: <list with value differences>
Value metric: <unit> - why: <scales with value, predictable, measurable>
Model: <e.g. good-better-best, per active seat, annual discount 15-20%>
| Tier | Target segment | Price | Key limits | Gated features |
Evidence: PSM range <x-y> (n=..., segment ...), interviews (n=...), cost floor <z>
Risks: <cannibalization, bill shock, migration churn>
Tests to run: <live test or follow-up research>
Decision owner: <name>; legal/finance review needed for <items>
Gotchas
- Copying the market leader’s price inherits their cost structure and segment, not yours.
- Too many discount levers (coupons, custom deals) destroy price integrity; set a discount policy with approval thresholds.
- Usage pricing without caps, alerts and a predictable minimum produces bill shock and churn.
- Annual-only plans raise cash but block small buyers; offer monthly at a premium.
- Survey respondents who are current customers anchor on what they already pay; segment them from prospects.
- This skill supports business pricing decisions; it is not financial or legal advice.
Verification
Check that each tier names its target segment, the value metric passes the “twice the value” and “predictable bill” tests, price points sit inside the measured acceptable range and above the cost floor, the script output matches the reported numbers, and any change to existing customers has a written plan and an approval step.
What the zip contains
Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Pricing Strategy"
short_description: "Price on the value metric, backed by WTP data"
default_prompt: "$pricing-strategy Help me choose between per-seat and usage-based pricing for our API product" scripts/van_westendorp.py
#!/usr/bin/env python3
"""Van Westendorp Price Sensitivity Meter from survey responses.
Usage:
python3 van_westendorp.py responses.csv [--segment-col plan --segment Pro]
CSV columns (case-insensitive): too_cheap, cheap, expensive, too_expensive
- one row per respondent, numeric prices in one currency and billing period
- optional extra columns (e.g. segment) are ignored unless --segment-col is set
Curve conventions (as in common PSM tooling):
too cheap share answering too_cheap >= p (falls with price)
not cheap 1 - share answering cheap >= p (rises)
not expensive 1 - share answering expensive <= p (falls)
too expensive share answering too_expensive <= p (rises)
PMC = too cheap x not cheap OPP = too cheap x too expensive
IPP = cheap x expensive PME = too expensive x not expensive
Respondents whose answers are not ordered too_cheap <= cheap <= expensive <=
too_expensive are dropped and counted. Exits non-zero on bad input or n < 30.
Standard library only.
"""
import argparse
import csv
import sys
COLS = ["too_cheap", "cheap", "expensive", "too_expensive"]
def load(path, seg_col, seg_val):
rows, dropped = [], 0
with open(path, newline="", encoding="utf-8-sig") as fh:
reader = csv.DictReader(fh)
if not reader.fieldnames:
sys.exit("error: empty CSV")
reader.fieldnames = [f.strip().lower() for f in reader.fieldnames]
missing = [c for c in COLS if c not in reader.fieldnames]
if missing:
sys.exit(f"error: missing columns: {', '.join(missing)}")
if seg_col and seg_col.lower() not in reader.fieldnames:
sys.exit(f"error: segment column '{seg_col}' not found")
for row in reader:
if seg_col and (row.get(seg_col.lower()) or "").strip() != seg_val:
continue
try:
vals = [float(str(row[c]).replace(",", "").strip().lstrip("$")) for c in COLS]
except ValueError:
dropped += 1
continue
if not (vals[0] <= vals[1] <= vals[2] <= vals[3]) or vals[0] >= vals[3]:
dropped += 1
continue
rows.append(vals)
return rows, dropped
def curves(rows, p):
n = len(rows)
too_cheap = sum(r[0] >= p for r in rows) / n
cheap = sum(r[1] >= p for r in rows) / n
expensive = sum(r[2] <= p for r in rows) / n
too_exp = sum(r[3] <= p for r in rows) / n
return {
"too_cheap": too_cheap,
"cheap": cheap,
"not_cheap": 1 - cheap,
"expensive": expensive,
"not_expensive": 1 - expensive,
"too_expensive": too_exp,
}
def crossing(rows, grid, a, b):
"""First price where curve a (falling) meets or drops below curve b (rising)."""
prev_p, prev_d = None, None
for p in grid:
c = curves(rows, p)
d = c[a] - c[b]
if d <= 0:
if prev_d is None or prev_d == d:
return p
return prev_p + (p - prev_p) * prev_d / (prev_d - d)
prev_p, prev_d = p, d
return None
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("csv_path")
ap.add_argument("--segment-col")
ap.add_argument("--segment")
ap.add_argument("--steps", type=int, default=2000, help="price grid resolution")
args = ap.parse_args()
if bool(args.segment_col) != bool(args.segment):
sys.exit("error: use --segment-col and --segment together")
rows, dropped = load(args.csv_path, args.segment_col, args.segment)
print(f"respondents used: {len(rows)} dropped (inconsistent or non-numeric): {dropped}")
if len(rows) < 30:
sys.exit("error: fewer than 30 valid respondents; results would be unstable")
lo = min(r[0] for r in rows)
hi = max(r[3] for r in rows)
grid = [lo + (hi - lo) * i / args.steps for i in range(args.steps + 1)]
points = {
"PMC point of marginal cheapness": crossing(rows, grid, "too_cheap", "not_cheap"),
"OPP optimal price point": crossing(rows, grid, "too_cheap", "too_expensive"),
"IPP indifference price point": crossing(rows, grid, "cheap", "expensive"),
"PME point of marginal expensiveness": crossing(rows, grid, "not_expensive", "too_expensive"),
}
for label, value in points.items():
print(f"{label:<40} {'n/a' if value is None else f'{value:,.2f}'}")
pmc = points["PMC point of marginal cheapness"]
pme = points["PME point of marginal expensiveness"]
if pmc is not None and pme is not None:
print(f"\nacceptable price range: {pmc:,.2f} - {pme:,.2f}")
if pmc >= pme:
print("warning: PMC >= PME; the sample does not agree on a range")
if dropped / max(1, len(rows) + dropped) > 0.2:
print("warning: over 20% of answers were inconsistent; check question order and wording")
if __name__ == "__main__":
main()
About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.