Headless: what you own and what you're still renting

Every agency selling headless says "full code ownership, no lock-in". Ours says it too. It's on the same page as "senior engineers" and "built to scale", which should tell you how much information it carries.
The phrase is nearly free to write and expensive to mean. Here's how to find out which one you're getting.
1. Did they fork the engine?
This is the one that decides the next five years.
If a team needed the commerce engine to behave differently and edited the engine to do it, you now own a fork. That sounds like ownership. It's the opposite: every upstream release — security patches included — becomes a merge conflict somebody has to resolve, and that somebody is whoever you're paying at the time.
The alternative is that everything custom lives beside the engine as modules, and the engine stays stock and upgradeable. Ours has seven custom modules — search, activity, the agent, restock, insights — and zero forks. When the engine ships a release we take it.
Ask directly: have you modified the engine's own code, or only added to it? A straight answer takes ten seconds. Watch for "we've extended it heavily", which is not an answer to the question you asked.
2. Can you delete a feature?
Better test than any architecture diagram, and it's the one we use on ourselves.
We deliberately tried to remove our chat feature — not the code, the whole thing. Not because we wanted to; because if you can't remove a piece cleanly, the pieces aren't actually separate and "composable" was a word on a slide.
The first attempt failed. Chat and search were sharing catalog logic that had quietly grown chat-shaped, so pulling one dragged the other. We moved that shared logic into a layer that knows about products and carts and nothing about any feature, with one rule: nothing in it may import from a feature folder. After that, chat came out cleanly and search, recommendations and the ops agent didn't notice.
Ask: if we drop this feature in a year, what else breaks? If the answer is a shrug, nobody's tried.
3. Where do the credentials live?
Less obvious, and it's the one that bites at handover.
If your Stripe key, your S3 bucket, and your provider tokens are in the vendor's account, on the vendor's infrastructure, then you own source code you cannot deploy. That's a novel form of lock-in — technically you have everything, practically you have a zip file.
The version that works: same image, different environment. Every credential is config, the repo is yours, and it runs wherever you put it. If you can't take the code, add your own env vars, and deploy it to your own account without a phone call, you're renting.
One nuance worth knowing, because vendors sometimes hide behind it. Some things genuinely can't be moved into a settings page — a payment provider is built once when the process starts, so its key has to be there before boot. That's a real constraint, not evasion. "It's an env var" is fine. "It's in our account" is not.
What ownership is actually worth
None of this matters while you're happy. It matters on exactly two days: the day you want to change vendors, and the day your vendor stops answering.
On those days, the questions are simple. Can someone else run this? Can they upgrade it? Can they understand it in a week? A stock engine with custom modules beside it passes all three. A forked engine with tangled features and credentials in someone else's account passes none — and all of them were sold as "full code ownership".
Ask the three questions before you sign, not on the day you need the answer to be yes.
