This site has a cost estimator. A visitor answers a few questions about their project, and a minute later they get a price range, a breakdown, a smaller first phase they could start with, and a comparison with typical agency prices. Behind it is Claude, Anthropic’s AI model.
The first version worked, in the sense that it always returned a sensible-looking estimate. The trouble was that the numbers were well above what I’d quote, and above what competitors charge. A tool meant to start conversations was scaring people off.
Fixing it taught me more about building with AI than getting it working did, so here’s what happened.
Version 1: ask the AI for a price
The first version gave Claude the visitor’s answers and asked for “a realistic market estimate”:
Price in the currency of the client’s business country… Base rates on what a professional freelancer or small studio typically charges where the client expects the development team to be.
It looks reasonable. In practice it had three problems:
- “Market rate” has no single answer. The same five-page site can cost a few thousand rupees from a template shop or several lakh from an agency, and both are “the market”. With no anchor, the model leaned towards the established, documented end of the range, which is mostly agency pricing.
- The model didn’t know my rates. It was estimating what “a professional” charges, not what I charge. I work alone, with no account managers or office overheads, so I’m well below agency prices.
- Arithmetic and policy were hidden inside the answer. Currency, rounding, rush premiums and the breakdown all came back as one opaque number. When a price looked wrong, I couldn’t tell which part was wrong.
Version 2: the AI estimates hours, the code sets the price
The fix was to split the job along the line between judgement and arithmetic.
Claude now does only what it’s good at: reading a messy brief and judging the effort involved. It returns hours, not money, as structured data:
const EffortSchema = z.object({
timeline: z.string(),
breakdown: z.array(z.object({ item: z.string(), lowHours: z.number(), highHours: z.number() })),
phaseOne: z.object({ scope: z.string(), lowHours: z.number(), highHours: z.number() }),
costDrivers: z.array(z.string()),
assumptions: z.array(z.string()),
summary: z.string(),
});
The prompt gives it benchmarks to calibrate against, which I set from how long this kind of work takes me:
- Business website, 6–15 pages, with a custom design: 50–90 hours
- E-commerce store on Shopify or WooCommerce with standard features: 50–110 hours
- SaaS or web application MVP in Next.js: 150–350 hours
- Each third-party integration: 4–15 hours
My code then does everything that’s policy or arithmetic:
- the hourly rate for the visitor’s country (for example ₹800 in India, US$20 in the US);
- currency and rounding to figures a person would quote;
- a 20% rush premium for urgent timelines;
- the “typical agency price” comparison, using typical agency hourly rates for each market;
- checks for nonsense, such as negative hours or a high end below the low end.
What changed
Here’s a sample: a mid-sized business website for a client in India, where the model estimated 52–82 hours.
| Version 2 result | |
|---|---|
| My estimate | ₹42,000 – ₹66,000 |
| A smaller first phase | ₹20,000 – ₹28,000 |
| Typical agency price for the same work | ₹1,35,000 – ₹2,35,000 |
The same hours for a client in the US come to US$1,050–1,650, against an agency range of US$6,700–10,100.
The price is now explainable, line by line. If I change my rates, I change one number in the code, not the prompt. And the agency comparison does something version 1 couldn’t: it shows visitors why working with a freelancer costs less, instead of quoting agency prices at them.
Guardrails that matter in production
Getting an AI feature to work in a demo is easy. Making it safe on a public website takes more:
| Risk | What the estimator does |
|---|---|
| The model returns malformed data | Structured outputs: the API must return JSON matching the schema, which is validated before use |
| A visitor types instructions into a form field (“ignore the above…”) | Answers are wrapped in <project_answers> tags, and the prompt says to treat them as description, never instructions |
| The model declines or stops early | Refusals and truncated answers are caught, and the visitor is asked to try again rather than shown half an estimate |
| Impossible numbers | Code clamps negative hours and inverted ranges, and rejects an estimate with no effort |
| Anyone using it to run up a bill | The API key stays on the server, and each visitor is limited to five estimates an hour |
What each estimate costs
I counted the tokens in a typical request with Anthropic’s free token-counting endpoint: 2,647 input tokens, including the system prompt, the visitor’s answers and the output schema. The output varies with the project, so I’ve assumed 1,000–2,000 tokens.
| Model | Price per million tokens (input / output) | Per estimate | Per 1,000 estimates |
|---|---|---|---|
| Claude Opus 5.5 (what I use) | $4 / $20 | $0.031 – $0.051 | $31 – $51 |
| Claude Sonnet 5.5 | $2 / $10 | $0.015 – $0.025 | $15 – $25 |
| Claude Haiku 5.5 | $0.10 / $0.50 | $0.0008 – $0.0013 | $0.76 – $1.26 |
Output costs five times as much as input per token, so it makes up about three-quarters of the bill. At a few rupees per estimate, the most capable model is affordable for a lead-generation tool. A single client won from a thousand estimates pays for it many times over. For a high-volume feature, such as categorising every support ticket, the cheaper models would be the obvious choice.
What I’d tell anyone adding AI to a website
- Let the AI judge and the code calculate. Models are good at reading messy human input and weighing it. Prices, taxes and business rules belong in code you can test.
- Give it your own benchmarks. Without an anchor, a model gives you the internet’s average, and that may not be what you charge.
- Ask for structured output. It turns “a paragraph that usually contains a number” into data you can validate.
- Treat visitor input as data, never as instructions, and design for the model saying no.
- Show ranges, not false precision. Every page of the estimator says it’s a ballpark for planning, not a quote. That’s honest, and it starts a better conversation.
The estimator works now. Not because the AI got cleverer, but because the arithmetic around it is mine.