How to write a specification a supplier can quote against
A supplier can only fix a price for work that is written down. This guide covers what to put in the document, what to leave out and how to handle what you do not know yet.
A fixed price is a promise to deliver a defined thing for a defined sum. If the thing is not defined, the supplier has two choices. They can pad the price to cover the unknowns, or they can quote low and recover the difference through change requests. Neither is dishonest. Both follow from a vague brief.
A written specification also lets you compare quotes. Three suppliers who quote against one page of bullet points are pricing three different systems. The cheapest quote may simply describe the smallest system.
Put seven things in the specification
None of the seven needs technical language. All need plain, specific statements.
- What the system does. List the functions as actions by a named role, such as "a branch officer registers a new customer". Number each one so a quote can refer to it.
- What it does not do. Name the things a reader might assume are included and are not: payroll, a mobile app, a second language, migration of old data.
- The data it holds. List the main records, their important fields, how long you keep each record and which ones are sensitive.
- Who can do what. A table of roles against actions. Include approval steps and any rule that the person who raises an item cannot approve it.
- The systems it connects to. For each one, state what data moves, in which direction, how often, who owns the other system and whether its interface is documented.
- Volumes and limits. Users, records per year, peak transactions per hour, file sizes, expected response times and the longest outage you can tolerate.
- Acceptance tests. Statements that say how both sides will know the system works.
The second item does more work than it seems to. Scope disputes tend to be about something one side assumed and the other never priced. A list of exclusions settles those arguments before they start.
Write acceptance tests as observable statements
An acceptance test is a statement a person can watch and mark as pass or fail. It names a starting condition, an action and a result you can see. If two reasonable people could watch the same screen and disagree about the result, rewrite the test.
- Weak: "the system is fast". Better: "with 50,000 customer records loaded, a search by identity number shows results within 2 seconds".
- Weak: "the system is secure". Better: "a clerk who opens the approval screen sees an access refused message, and the attempt appears in the audit log".
- Weak: "reports are accurate". Better: "the month-end total on the sales report equals the sum of that month's invoices in the agreed test data".
- Weak: "easy to use". Better: "a new clerk, given the one-page guide, registers a customer without help in under 5 minutes".
Write at least one test for every numbered function. Add tests for the cases that go wrong: the duplicate record, the missing field, the other system being down. Agree the test data as well, or nobody can run the tests.
Rule of thumb: a requirement with no acceptance test is a wish. Write the test or take the requirement out.
Say what you do not know yet
No buyer knows everything at the start. The mistake is to hide the gaps behind confident wording. The supplier will find them later, and the price will move then, when you have less room to negotiate. Put the unknowns in the document under their own heading.
- List each open question, who can answer it and by when.
- State an assumption for pricing: "assume the accounting system has a documented interface. If it does not, the supplier quotes this item again".
- Ask for a separate line price or a range for each uncertain item, so one unknown does not inflate the whole quote.
- Where the unknowns are large, pay for a short discovery first. Its output is the finished specification, and you should be free to take it to any supplier.
Leave out screen designs and technology choices
A specification says what the system must do, not how it looks or how it is built. Detailed screen designs fix decisions before anyone has tested them with users. A rough sketch of a difficult screen is fine. Mark it as an illustration.
Leave technology choices to the supplier unless they are real constraints. A real constraint has a reason you can state. Your team can maintain only one programming language. The data must stay in the country. State the constraint and the reason. A preference written as a requirement narrows your choice of suppliers for no gain.
Leave out adjectives too. Nobody can price or test "modern", "intuitive" or "scalable". Replace each one with a number or a test, or delete it.
Keep it as short as the system allows
Length follows the system, not ambition. As a rough guide, a small internal tool might need 8 to 12 pages. A system with several roles, a few connections and regulated records might need 25 to 40. Beyond that, readers skim. Use numbered statements and tables, not essays. Put a glossary at the front so every term means one thing.
Give the document a version number and a change log. Once both sides sign it, any change goes through a written change request with its own price. That is how a fixed price stays fixed.
Check the specification before you send it
- Every function is numbered and written as an action by a named role.
- A list states what the system does not do.
- The document lists the main records, their fields and their retention periods.
- A table shows which role can do what, including approvals.
- Every connected system is named, with direction, frequency and owner.
- Volumes, peak loads and response times have numbers.
- Every function has at least one acceptance test a person can observe.
- Open questions and pricing assumptions have their own section.
- Screen designs and technology preferences are out. Real constraints are in, with reasons.
- The document has a version number, a glossary and a place for both signatures.
Stack9 starts every project with a written specification of this kind, and the client signs it off before we write code. We quote a fixed scope against it, and its acceptance tests run before go-live.