The brief sounded small. "Let users connect their Meta Business Profile themselves." The reality was not small. The underlying surface spans several capabilities, and they are not independent. Some steps cannot happen until others have. Some fail silently. Some require permissions the user does not know they need until they are already blocked.
So the actual problem was never "call the API." The actual problem was: how do you take something that assumes an engineer's mental model and hand it to a founder who just wants to sell, and have them finish, alone, without support tickets.
Why not just expose the steps?
The tempting move is to mirror the API in the UI. One screen per capability. It feels honest. It is also a trap, for a reason worth stating plainly:
Users do not have a model of your system. They have a model of their goal. Every place those two models diverge is a place they get stuck.
The API's structure is optimized for the machine. It groups things by capability. But the user does not think in capabilities. They think "I want my business to show up and take payments." If the flow is organized around the API, the user is forced to learn the API's shape just to complete a task they never wanted to understand. That is where drop-off comes from.
So the first real decision was to throw away the API's ordering as a UX principle and rebuild the sequence around the user's goal, then map that sequence back onto what the API requires under the hood.
How do you find the real sequence?
I did this in a boring, mechanical way, because boring and mechanical is what prevents surprises at build time.
- List every capability the flow touches, ignoring UI entirely.
- For each one, write its prerequisites. Not the happy path. The actual "this will fail unless X is already true" conditions.
- Build the dependency graph. Once you see what truly blocks what, the legal orderings fall out on their own. Most of the "steps" you imagined turn out to be fake choices. The graph only allows a few real sequences.
- Collapse anything the user should not have to think about into a step they never see.
What came out was five steps. Not because five is a nice number, but because that is how many decision points require the user. Everything else was either automatic, or a prerequisite check that belonged behind the scenes.
The number of steps in a good wizard equals the number of decisions only the user can make. Not the number of API calls, not the number of capabilities, not the number of screens that felt natural to design. Every step beyond that count is you making the user do your system's bookkeeping.
What goes in a PRD that engineering can build from?
This is where a lot of PRDs fail. They describe the feature and leave the hard parts as "TBD in design" or "handle errors gracefully." Those phrases are where the project goes to die.
The version I delivered was written to be handed straight to an engineer with no meeting attached. That meant three things had to be non-negotiable.
A capability matrix, not prose
For each step: which API capabilities it touches, what must be true before it runs, what it writes, and what states it can leave the user in. A paragraph hides edge cases. A matrix forces you to fill in the cell for "what if this is already connected," which is exactly the cell everyone forgets.
Acceptance criteria written against the API version
Pinned to Graph API v25.0, not "the Meta API" in the abstract. Versions change behavior. A criterion that does not name its version is a criterion that expires without telling you.
Failure as a first-class path
The happy path is the easy 20 percent. The PRD spent most of its length on what the user sees when a prerequisite is missing, when a token expires mid-flow, when they abandon and return. If your spec is mostly happy path, you have not written a spec. You have written a demo.
The part that mattered more than the flow
I delivered the five-step wizard on spec. But the thing I am glad I did was what came after: I used the same dependency-graph method to sketch a roadmap of six more onboarding wizards, all built on the same architecture, and brought that to the client unprompted.
That reframed the engagement. It stopped being "here is the thing you asked for" and became "here is a repeatable way to onboard users onto anything, and here is what to build next." One is a deliverable. The other is a reason to keep working together.
The deliverable answers the question you were asked. The good version also answers the question they were about to ask next.
If you take one thing from this: do not model your UI on your API. Model it on the user's goal, then let the dependency graph tell you the smallest honest number of steps. The API is a constraint to satisfy, not a structure to inherit.