Ahmed Mostafa ← Work
Work Sample · PRD Excerpt

Meta Business Profile Wizard

Freelance client engagementGraph API v25.05 steps

People ask what a PRD from me looks like. Rather than describe it, here is the load-bearing part of a real one: the capability matrix, the acceptance criteria, and the failure paths. This is the section engineering builds from.

Redaction note

Client name, internal identifiers, commercial terms, and the full 40-page document are removed. The structure, the matrix columns, and the acceptance-criteria style are exactly as delivered. Capability names and values shown here are illustrative stand-ins for the client's real configuration.

01Scope & non-goals

A founder connects their own Meta Business Profile, alone, without a support ticket. The underlying surface is order-dependent and prerequisite-heavy. The wizard's job is to hide that, not to mirror it.

  • In scope: business asset creation or claim, Page linkage, Instagram professional account linkage, catalog provisioning, and permission grant.
  • Out of scope: ad account setup, payment instrument capture, and anything requiring Meta review turnaround. These block on a third party and cannot live inside a flow the user is expected to finish in one sitting.
  • Non-goal: exposing every capability the API offers. Coverage is not the objective. Completion is.

02The Decision-Point Rule

The number of steps in a 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.

Applying it here produced five steps, not because five is tidy, but because the dependency graph only contained five points where the system could not proceed without a human choice. Everything else became a precondition check the user never sees.

03Capability matrix

One row per step. A paragraph lets you skip the awkward cases; a matrix forces you to fill in the cell for "what if this is already connected", which is the cell everyone forgets.

StepCapability touchedPreconditionsWritesExit states
1 · Business Business asset create / claim
business_management
Authenticated user token. User is an admin of at least one Page or has none at all. business_id persisted against the tenant record. Created · Claimed existing · Already linked (skip forward) · Not an admin (hard stop, step 1a)
2 · Page Page selection & assignment
pages_show_list
Step 1 exit state is Created, Claimed, or Already linked. page_id, page access token reference. Selected · Created new · Zero eligible Pages (branch to create) · Page owned by another business (hard stop with named owner)
3 · Instagram IG professional account linkage Page exists and is admin-accessible. IG account is Professional, not Personal. ig_user_id bound to page_id. Linked · Skipped (explicitly optional, resumable later) · Personal account detected (inline conversion prompt)
4 · Catalog Commerce catalog provision
catalog_management
Business asset owns the Page. Business is not already at catalog quota. catalog_id, product feed endpoint. Provisioned · Reused existing catalog · Quota reached (hard stop, surfaces current count)
5 · Grant Permission grant & verification Steps 1, 2, 4 in a terminal success state. Scoped token stored server-side; onboarding marked complete. Granted · Partially granted (named gaps, resumable) · Declined (state preserved, no rollback)

The column that does the work is Exit states. Every bolded value in it is a case a prose spec would have called "handle gracefully" and left to be discovered during QA.

04Acceptance criteria

Pinned to a named API version. A criterion that does not name its version is a criterion that expires without telling you.

AC-2.3 · Page already owned elsewhere

Given the user selects a Page already owned by a business asset other than their own,

When the assignment call returns the ownership conflict on Graph API v25.0,

Then the wizard names the owning business in the error, offers "request access" as the only forward action, and does not advance the step counter.

AC-3.1 · Personal Instagram account

Given the connected Instagram account is Personal rather than Professional,

When the user reaches step 3,

Then the conversion prompt renders inline with the account handle shown, and step 3 remains skippable without invalidating steps 1, 2, 4 or 5.

AC-5.2 · Partial permission grant

Given the user grants a strict subset of the requested scopes,

When the grant callback resolves,

Then onboarding is marked incomplete, the missing scopes are listed by human-readable name (not scope string), and re-entry resumes at step 5 rather than step 1.

AC-0.4 · Token expiry mid-flow

Given the user's token expires between any two steps,

When the next call returns an auth error,

Then all completed step state is preserved server-side, re-authentication returns the user to the step they were on, and no already-written asset is duplicated.

05Failure paths

The happy path is the easy twenty percent. In the delivered document, this section was longer than the flow description. That ratio is deliberate.

  • Abandon and return. Every step is resumable. Nothing written in steps 1–4 is rolled back on exit, because rollback creates a worse failure than a stale asset.
  • Silent failure. Any call that can succeed at the transport layer while failing semantically gets an explicit post-condition read, not a status-code check.
  • Permission the user didn't know they needed. Surfaced as a precondition at the top of the step, before the user invests effort, never as an error after.
  • Already connected. Treated as a first-class success state on every step, not an edge case.

06What shipped after

The wizard was delivered on spec. The dependency-graph method behind it was then reused, unprompted, to sketch six further onboarding wizards on the same architecture. That turned a deliverable into a roadmap, and a project into an engagement.

The deliverable answers the question you were asked. The good version also answers the question they were about to ask next.