A founder forwarded me an invoice with one line on it. “Development - 38 hours.” That was the whole document. She’d paid a version of that line every month for most of a year and couldn’t tell me a single thing it had bought her.

She can’t read code. Almost nobody paying for it can. So the choice usually collapses into trusting completely or accusing loudly, and both are terrible ways to spend money.

You don’t need to read the code to work out whether the bill is fair. Everything around the code (who owns it, what changed and when, how finely the time was recorded, what’s on the invoice that isn’t labour at all) is readable by anyone. Padding leaves a paper trail. So does good work. They just leave different ones.

Send these by email rather than raising them in a meeting. You want the answers in writing, and the supplier to have time to produce them properly. A room rewards whoever thinks fastest on their feet, which is the wrong test.

I’ve been on the receiving end of all 6. I’ve also sent invoices vaguer than they should have been, when I ran an agency. Not to hide anything. Itemising takes time and nobody had ever asked.

The short version

  • Without the repo and its dated history, no other developer can check a single hour.
  • Neat 40-hour months and answers you can’t follow are both warning signs.
  • You may be paying twice for one feature, plus a hosting markup nobody mentioned.
  • Some big bills are honest. Timing tells you which ones.

1. Who owns the code, and where does it live?

Ownership is a contract question, not a moral one. If you don’t own the repository, you can’t get a second opinion from anyone, ever.

A good answer names the host and the account: “You own it. The repo is under your organisation on GitHub, I’ll add you as an owner today.” A bad answer is “it’s on our internal system”, or “we hand over at the end of the engagement”, or “the code is our intellectual property and you’re licensed to use it.” That last one is sometimes legitimate (proprietary platforms exist), but then it should have been priced and disclosed before you signed, not discovered when you asked.

2. Can I see the commit history for the period I’m being billed for?

A commit history is a timestamped record of what changed and when. You don’t have to understand the changes. You’re reading dates.

A good answer is an invitation to the repository or an exported log, with messages that read like work someone did: “fix tax calculation on NZ checkout”. A bad answer is a flat refusal, or “we don’t use version control”, or 6 weeks of billed work arriving as a single commit the day before the invoice. Commit messages that all say “update” aren’t proof of anything on their own. But there’s a real difference between a supplier who can show you the work and one who needs you to imagine it.

3. What were the hours spent on, and how finely is time recorded?

Granularity is the tell. Detailed entries mean someone tracked as they went. Round numbers mean someone reconstructed the month on invoice day.

A good answer is task-level lines with dates, recorded to the quarter hour or finer, describing things you recognise from your own conversations. A bad answer is “development”, “maintenance” and “general work” as line items, or a month that lands on exactly 40 hours the way every other month has. If you’re told “it’s a retainer, so we don’t itemise”, check what you actually bought. Fixed outcomes don’t need itemising. Hours do.

4. Why did that task take 3 days? Explain it as if I don’t write code.

This is the most useful question on the list. Someone who understands what they built can describe it in plain words. Jargon is where padding hides, because jargon ends the conversation.

A good answer has a shape you can follow: “Your product data arrived in 3 different formats from 3 different imports, so before I could touch the pricing rules I had to make them agree with each other.” A bad answer is still opaque after the second attempt, or arrives with a note of impatience, or is the sentence “it’s more complicated than you’d think.” Complexity is real and it’s often expensive. The test isn’t whether the work was hard. It’s whether anyone will tell you why.

5. Have I paid for this before?

Rebuilds are normal. Undisclosed rebuilds are how the same feature gets sold twice.

A good answer owns it: “Yes, and here’s why: the first version ran against an API the vendor retired in March.” A bad answer presents work as new when you distinctly remember paying for it, or reaches for “technical debt” as a reason without ever naming what the debt is or who ran it up. Debt someone else accumulated on your account is still worth an explanation.

6. What’s on this bill that isn’t someone’s time?

Hosting, licences, plugins, monitoring, third-party services. Passing those through is normal. Marking them up without saying so isn’t.

A good answer is an itemised list with real vendor names and a plain statement of what’s at cost and what carries a margin. A disclosed margin on managed hosting is a legitimate business model and I have no problem with it. A bad answer is “hosting and maintenance” as a single figure for 3 years, or a number that doesn’t survive 5 minutes on the vendor’s public pricing page. While you’re there, check whose name is on the accounts. Domain, hosting, analytics and payment gateway should all sit in your company’s name.

When expensive is just expensive

Some work costs a lot for reasons that have nothing to do with padding, and you should be able to recognise them before you accuse a good developer of inventing hours.

Anything touching money, tax, personal data or compliance is slow because being wrong is unrecoverable. Integrations with a system you don’t control are priced for the vendor’s bad documentation, not for your feature. Work on a codebase somebody else wrote carries an archaeology bill before a single line changes. Testing is often the largest invisible cost on the invoice, and review and security have got dearer, not cheaper, since AI started writing more of the code. And a senior hour that prevents 40 junior ones is the cheapest thing on the page, even though it’s the highest rate.

The strongest signal isn’t the size of the number. It’s when you heard about it. A supplier who tells you the estimate is going up before doing the work is managing you honestly. Padding shows up as a surprise. Competence shows up as a warning.

You’ll never be able to read the code. You can always tell whether someone wants you to.