A signup form calls a phone validation API and gets neither valid nor invalid. Two fixture branches produce that shape, unknown and provider_failure, and both return state unknown with valid null. Only the next field tells them apart. This article uses the checked four-branch synthetic example to show why your code should route them differently, what the fixture outputs exactly, and what Basic Lookup can never prove: possession, ownership or OTP delivery.
The problem: two nulls that mean different things
Your signup handler receives a phone result where valid is null and line_type is null. The decision you face is what happens to the person waiting on the form. A common shortcut treats every null as a rejection. That merges two situations. In one, the check ran and could not say anything useful about the number. In the other, the provider call itself failed, so nothing was learned at all. Rejecting in the second case punishes a user for an outage. Accepting blindly in either case pretends a check happened when it did not. The downloadable example makes the difference explicit in the next field, and your application should keep it explicit too.
Exact fixture outputs for the two ambiguous branches
Every branch of the local example returns mode synthetic_fixture, otp_sent false and live_line_type_available false. The unknown branch returns state unknown, valid null, line_type null and next review_or_existing_otp. The provider_failure branch returns state unknown, valid null, line_type null and next bounded_retry_then_review. For comparison, normal returns state simulated, valid true, line_type mobile and next continue_customer_policy, and otp returns state unsupported, valid null, line_type null and next customer_otp_provider. An unsupported branch name raises UNSUPPORTED_FIXTURE. Note that state alone cannot separate unknown from provider_failure; if your code switches only on state, you have already lost the distinction. The line_type mobile in the normal branch is a synthetic value: the current free Basic Lookup route does not request line type, which is why live_line_type_available is false everywhere.
A hypothetical scenario
Imagine a small B2B team whose signup form collects a phone number for account contact. This is a hypothetical design, not a description of any real deployment. A user submits a number. In test, the team runs the unknown branch: the proposed handler stores the attempt, lets signup continue into a review queue, and optionally triggers the team's existing OTP flow if it already has one. Next they run provider_failure: the proposed handler retries a bounded number of times with a short delay, and if the answer still does not arrive, it places the attempt in review rather than rejecting it. Finally they run otp and confirm the screen does not wait for a code, because otp_sent is false and the fixture never sends one. The retry count, delay and review rules are choices this team would make; the fixture only names the next step.
What is implemented versus what you design
Implemented: a dependency-free Python function that maps a branch name to a fixed dictionary, with no network calls and no credentials, plus unit tests. Not implemented: any retry loop, review queue, OTP sending or real provider adapter. Those are caller-owned. The anonymous HTTP trial is a separate three-scenario check and should not be confused with this four-branch downloadable example. Basic Lookup, where an eligible business enrolls for it, is an adjunct signal with a technical rate limit of 60 requests per minute; it does not establish possession, ownership, reachability or OTP delivery. If you need proof the person can receive a code, use your own OTP provider. If you need identity, that is a different check entirely.
Failure handling rules worth keeping
Keep retries bounded: a provider failure that loops forever becomes a hung signup. Record which branch produced a decision, so a later reviewer can see that a null came from a failure rather than a verdict. Never convert unknown or provider_failure into an automatic rejection. Treat an unrecognized branch or error as a hard stop in tests, mirroring UNSUPPORTED_FIXTURE. Do not let a synthetic valid true unlock anything real; a fixture result is not live business permission. Search interest in phone validation APIs is not evidence that any specific design works for your users.
Acceptance checklist
Before deciding whether a sandbox integration fits your task, confirm: all four branches run locally and match the outputs above field for field; your handler distinguishes unknown from provider_failure using next, not state; provider_failure has a retry limit and a review fallback; no branch leads to automatic rejection solely because valid is null; your UI never waits for a code when otp_sent is false; no code path reads line_type from a live Basic Lookup result; logs record mode synthetic_fixture so test data cannot be mistaken for production; and the team agrees in writing that a phone check proves neither possession, ownership nor identity. If all pass, the next step is an optional saved sandbox request, not a live rollout.
Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.
Top comments (0)