From bee88fdd8eb128cc3dc836eec69b12ad73b2d1af Mon Sep 17 00:00:00 2001
From: Leif Hagen
Date: Wed, 10 Jun 2026 16:56:15 -0400
Subject: [PATCH 1/2] feat: sectional onboarding tester for first-request page
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replaces the one-size-fits-all on
making-your-first-request.mdx with a three-section
matching the three primary API use cases: person search, reverse phone
lookup, and property search.
The legacy tester only made one boilerplate request
(`name=John Smith&city=Seattle&state_code=WA`), which gave a trial user
who just received their key no visibility into how the API performs
against their own use case.
Behavior:
- Shared API key field at the top, then three stacked sections. Each
section has the form fields its use case actually needs (e.g. phone
section asks for a phone number; property section asks for street and
state), a Send button, an inline response panel, and a Link to the
deeper guide for that use case.
- The proxy at /docs/api/test-proxy is now a dynamic route under
/[endpoint] with an allowlist (`person`, `property`), so the property
section can hit /v2/property while the person/phone sections continue
to hit /v2/person.
- 4xx responses get human-readable classification rather than a generic
failure — 403 is "invalid key", 429 is "rate limit", 404 is "no
results found, your key is working".
Analytics:
- Per-section events on Send (with the outgoing query params), on
response (with success/status/result_count), and on guide-link
click. The result_count signal sniffs the v2 response shape so we
can tell whether a section is producing empty results.
- The legacy single-shot test event is replaced by these
section-scoped events.
Removed:
- src/components/activation/api-key-tester.tsx
- src/app/api/test-proxy/route.ts (replaced by the dynamic route)
---
.../making-your-first-request.mdx | 8 +-
.../api/test-proxy/{ => [endpoint]}/route.ts | 15 +-
src/components/activation/api-key-tester.tsx | 272 -----------
.../activation/onboarding-tester.tsx | 447 ++++++++++++++++++
src/mdx-components.tsx | 4 +-
5 files changed, 466 insertions(+), 280 deletions(-)
rename src/app/api/test-proxy/{ => [endpoint]}/route.ts (58%)
delete mode 100644 src/components/activation/api-key-tester.tsx
create mode 100644 src/components/activation/onboarding-tester.tsx
diff --git a/content/docs/documentation/making-your-first-request.mdx b/content/docs/documentation/making-your-first-request.mdx
index c1414da..6d78ab2 100644
--- a/content/docs/documentation/making-your-first-request.mdx
+++ b/content/docs/documentation/making-your-first-request.mdx
@@ -4,11 +4,13 @@ description: A beginner-friendly guide to making your first Whitepages API reque
icon: Play
---
-Ready to test your API key? You can try it right here in your browser, or follow the step-by-step guide using Postman.
+Ready to test your API key? Pick the use case that matches what you're building and try it right here in your browser — or follow the step-by-step guide using Postman.
-## Quick Test
+## Try It Now
-
+Paste your API key once, then run a sample request for the use case you care about. Each section links to a deeper guide if you want to learn more.
+
+
## Using Postman (No Code Required)
diff --git a/src/app/api/test-proxy/route.ts b/src/app/api/test-proxy/[endpoint]/route.ts
similarity index 58%
rename from src/app/api/test-proxy/route.ts
rename to src/app/api/test-proxy/[endpoint]/route.ts
index eeeb47c..a5af7e8 100644
--- a/src/app/api/test-proxy/route.ts
+++ b/src/app/api/test-proxy/[endpoint]/route.ts
@@ -1,11 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
-export async function GET(request: NextRequest) {
+const ALLOWED_ENDPOINTS = new Set(["person", "property"]);
+
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ endpoint: string }> },
+) {
+ const { endpoint } = await params;
+ if (!ALLOWED_ENDPOINTS.has(endpoint)) {
+ return NextResponse.json({ error: "Invalid endpoint" }, { status: 404 });
+ }
+
const apiKey = request.headers.get("X-Api-Key");
const { searchParams } = new URL(request.url);
-
const queryString = searchParams.toString();
- const url = `https://api.whitepages.com/v2/person?${queryString}`;
+ const url = `https://api.whitepages.com/v2/${endpoint}?${queryString}`;
try {
const response = await fetch(url, {
diff --git a/src/components/activation/api-key-tester.tsx b/src/components/activation/api-key-tester.tsx
deleted file mode 100644
index 2af214d..0000000
--- a/src/components/activation/api-key-tester.tsx
+++ /dev/null
@@ -1,272 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { buttonVariants } from "@/components/ui/button";
-import amplitude from "@/lib/amplitude";
-
-interface ApiResponse {
- success: boolean;
- message: string;
- details?: string;
- data?: unknown;
- status?: number;
-}
-
-const MOCK_DATA = [
- {
- id: "P1234567890",
- name: "John Smith",
- age_range: "35-44",
- current_addresses: [
- {
- id: "A9876543210",
- address: "123 Main St, Seattle, WA 98101",
- is_current: true,
- },
- ],
- phones: [
- {
- number: "(206) 555-0198",
- type: "mobile",
- is_primary: true,
- },
- ],
- emails: [
- {
- address: "john.smith@email.com",
- score: 88,
- },
- ],
- },
-];
-
-interface ApiKeyTesterProps {
- mockMode?: boolean;
-}
-
-export function ApiKeyTester({ mockMode = false }: ApiKeyTesterProps) {
- const [apiKey, setApiKey] = useState(mockMode ? "demo-api-key-xxxxx" : "");
- const [isLoading, setIsLoading] = useState(false);
- const [response, setResponse] = useState(
- mockMode
- ? {
- success: true,
- message: "Success! Your API key is working.",
- data: MOCK_DATA,
- status: 200,
- }
- : null,
- );
-
- const testApiKey = async () => {
- if (!apiKey.trim()) {
- setResponse({
- success: false,
- message: "Please enter an API key",
- });
- return;
- }
-
- setIsLoading(true);
- setResponse(null);
-
- try {
- const result = await fetch(
- "/docs/api/test-proxy?name=John%20Smith&city=Seattle&state_code=WA",
- {
- headers: {
- "X-Api-Key": apiKey.trim(),
- },
- },
- );
-
- const data = await result.json().catch(() => null);
-
- if (result.ok) {
- amplitude.track("WPAPIDocsApiKeyTested", {
- success: true,
- status: result.status,
- });
- setResponse({
- success: true,
- message: "Success! Your API key is working.",
- data,
- status: result.status,
- });
- } else if (result.status === 403) {
- amplitude.track("WPAPIDocsApiKeyTested", {
- success: false,
- status: result.status,
- error: "invalid_key",
- });
- setResponse({
- success: false,
- message: "Invalid API key",
- details:
- "The API key you entered is not valid. Please check your email for the correct API key.",
- status: result.status,
- });
- } else if (result.status === 429) {
- amplitude.track("WPAPIDocsApiKeyTested", {
- success: false,
- status: result.status,
- error: "rate_limited",
- });
- setResponse({
- success: false,
- message: "Rate limit exceeded",
- details:
- "Your API key is valid but you've exceeded the rate limit. Wait a moment and try again.",
- status: result.status,
- });
- } else {
- amplitude.track("WPAPIDocsApiKeyTested", {
- success: false,
- status: result.status,
- error: "request_failed",
- });
- setResponse({
- success: false,
- message: `Request failed (${result.status})`,
- details: data?.message || "An unexpected error occurred.",
- data,
- status: result.status,
- });
- }
- } catch {
- amplitude.track("WPAPIDocsApiKeyTested", {
- success: false,
- error: "connection_error",
- });
- setResponse({
- success: false,
- message: "Connection error",
- details:
- "Unable to connect to the API. Please check your internet connection and try again.",
- });
- } finally {
- setIsLoading(false);
- }
- };
-
- const handleKeyDown = (event: React.KeyboardEvent) => {
- if (event.key === "Enter" && !isLoading) {
- testApiKey();
- }
- };
-
- return (
-
-
Try It Now
-
- Enter your API key below to make a test request and see real results.
-
+ Your API key is shared across all use cases below.
+
+
+
+ {SECTIONS.map((config) => (
+
+ ))}
+
+ );
+}
diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx
index 75b0373..fc6487e 100644
--- a/src/mdx-components.tsx
+++ b/src/mdx-components.tsx
@@ -1,7 +1,7 @@
import defaultMdxComponents from "fumadocs-ui/mdx";
import { Step, Steps } from "fumadocs-ui/components/steps";
import { APIPage } from "@/components/openapi/api-page";
-import { ApiKeyTester } from "@/components/activation/api-key-tester";
+import { OnboardingTester } from "@/components/activation/onboarding-tester";
import { RegionSearch } from "@/components/regions/region-search";
import { WebhookWalkthrough } from "@/components/webhooks/walkthrough";
import type { MDXComponents } from "mdx/types";
@@ -12,7 +12,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
Steps,
Step,
APIPage,
- ApiKeyTester,
+ OnboardingTester,
RegionSearch,
WebhookWalkthrough,
...components,
From f55069684f32dfe684d3060e4599acbe0fcff58b Mon Sep 17 00:00:00 2001
From: Leif Hagen
Date: Wed, 10 Jun 2026 16:56:23 -0400
Subject: [PATCH 2/2] fix: drop /docs prefix from walkthrough completion links
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The three Link hrefs on the webhook walkthrough completion screen
hard-coded the basePath (`/docs/documentation/...`), but next/link's
Link auto-applies the `basePath: "/docs"` from next.config.mjs — so
the rendered URLs were doubly-prefixed (`/docs/docs/documentation/...`)
and 404'd. Surfaced while reviewing the same pattern in the new
onboarding tester.
Drop the `/docs` prefix to match the convention used in MDX content
and in changelog-banner.tsx — bare paths that Link prefixes
automatically.
Reachable via /docs/documentation/webhooks/quickstart after completing
the walkthrough flow.
---
src/components/webhooks/walkthrough.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/webhooks/walkthrough.tsx b/src/components/webhooks/walkthrough.tsx
index 63bd8cb..a5013ea 100644
--- a/src/components/webhooks/walkthrough.tsx
+++ b/src/components/webhooks/walkthrough.tsx
@@ -571,19 +571,19 @@ export function WebhookWalkthrough() {