Skip to content
Courtix
September 25, 2026

How we estimate a fixed-scope project

Turning a discovery document into a number a client can hold us to: how we break work into estimable units, size the unknowns, and decide what isn't in the price.

A client asks "how much will this cost?" and wants one number, not a range with three asterisks. Giving them that number honestly — one we can actually hold to — is a different skill from writing code, and it’s one we take as seriously as the build itself.

We estimate units of work, not projects

"Build the customer portal" is not an estimable thing. We break the scoped work into pieces small enough that an engineer who’s built something similar can size each one with a straight face: "auth against the existing SSO provider," "CSV import with these three edge cases," "webhook receiver for the billing provider." Each piece gets its own estimate, in days, from the engineer who’d actually build it.

If a piece can’t be broken down further and someone is still shrugging at the estimate, that’s a signal the piece isn’t scoped yet, not a signal to guess harder. It goes back to discovery.

Every unit gets a confidence tag, not just a number

Alongside the day count, every unit gets tagged known, researched, or unknown:

  • Known — we’ve built this exact thing before. Low padding.
  • Researched — we’ve read the third-party docs, maybe run a spike, but haven’t shipped against this specific API or dataset. Moderate padding.
  • Unknown — depends on a system we haven’t seen the internals of, a dataset we haven’t profiled, or a vendor who hasn’t answered a technical question yet. This doesn’t go into a fixed-price number at all — it goes back to the client as a question to resolve, or into a time-and-materials spike we run first.

This is the single biggest lever against the classic fixed-price failure mode: a "known" number quietly built on top of three "unknown" assumptions.

Buffer per unit, not one number at the bottom

We don’t add a flat 20% contingency line to the total — that number is defensible to no one and gets negotiated away in the first pricing conversation. Instead, padding lives on individual units, sized to their confidence tag, and rolls up transparently. If a client asks "why does the CSV import cost more than the login screen," we can point at the specific integration risk, not a vibe.

We also compare the rolled-up total against our own delivery history on similar work. If an estimate comes in well under what a comparable engagement actually took, that’s treated as a bug in the estimate, not a win for the client.

The assumptions list is part of the price

Every fixed-price quote ships with an explicit assumptions list: which browsers, which data volumes, which third-party accounts already exist and are configured, which integrations are stable and documented versus reverse-engineered from a Postman collection someone found. The price is only valid if those assumptions hold.

This isn’t fine print to protect us after the fact — we walk through it with the client before signing, specifically so nobody is surprised later. When an assumption turns out to be wrong once the work starts, that’s a scope conversation, not a fight.

What triggers a change order, decided in advance

We agree on the change-order trigger before the first line of code, not when the first one is needed:

  • A requirement that wasn’t in the written scope.
  • An assumption from the list above turning out to be false.
  • A third-party system behaving differently than its documentation claimed.

When one of these hits, we stop, write down what changed and what it costs, and get sign-off before continuing. We don’t absorb scope creep silently to keep the relationship smooth — that just moves the pain to the next project, where the client expects the same silent absorption and we can’t deliver it.

We say no to fixed-price when the inputs say no

If, after discovery, more than a small fraction of the work is genuinely "unknown," we tell the client fixed-price isn’t the right shape for this engagement and propose time-and-materials with a capped discovery spike instead. Turning down a fixed-price deal we don’t believe in costs us less than delivering one badly.

Why it matters

Anyone can hand a client a number. The number that matters is the one the team can still stand behind in month three, when the CSV files turn out to be messier than the sample and the SSO provider’s docs were wrong about a header. Estimating this way doesn’t eliminate surprises — nothing does — but it means every surprise traces back to a documented assumption, not a guess dressed up as a fact. That’s what a client is actually buying when they buy a fixed price from us.