WhatDoc’s API Playground transforms static API documentation into an interactive testing environment. Users can send real HTTP requests, modify headers and payloads, and see live responses — all without leaving your docs.
Overview
The API Playground is automatically generated for backend/API projects when the AI engine detects endpoint definitions in your codebase. Each endpoint gets an embedded playground block that users can expand and interact with.
Playground blocks are only generated for API/backend repositories. Frontend and library projects skip playground generation.
How It Works
The AI engine includes special instructions for API projects:
// From server/services/llm.js (system prompt)
FOR API / BACKEND REPOS ONLY — INTERACTIVE PLAYGROUND BLOCKS :
After each endpoint 's documentation, append a fenced code block with
the language tag "json-api-playground" containing a JSON object with
the endpoint 's method, path, default headers, and a sample request body .
Example :
```json-api-playground
{
"method": "POST",
"endpoint": "/auth/signup",
"headers": { "Content-Type": "application/json" },
"body": {
"email": "[email protected] ",
"password": "securepass123",
"username": "newuser"
}
}
Rules:
ONLY generate for backend/API repos
JSON must be valid (no trailing commas)
Use realistic sample values from model schemas
For GET/DELETE, omit “body” or set to
Include auth headers if required
## Playground UI
The playground is a collapsible component that renders from the JSON config:
### Collapsed State
```jsx
<button onClick={() => setExpanded(e => !e)} className="w-full flex items-center justify-between">
<div className="flex items-center gap-3">
<span className={`px-2.5 py-0.5 rounded-md text-[11px] font-bold font-mono border ${
METHOD_COLORS[methodUpper]
}`}>
{methodUpper}
</span>
<span className="text-sm font-mono text-zinc-300">{endpoint}</span>
</div>
<div className="flex items-center gap-2 text-zinc-500 text-xs">
<span>Try it</span>
{expanded ? <ChevronUp /> : <ChevronDown />}
</div>
</button>
Visual Design:
Color-coded HTTP method badge (GET=green, POST=blue, DELETE=red)
Endpoint path in monospace font
“Try it” CTA with chevron icon
Expanded State
When expanded, the playground reveals:
Base URL field (editable)
Headers editor (JSON textarea)
Request body editor (JSON textarea, hidden for GET/DELETE)
Send button with loading state
Response viewer with syntax highlighting
Request Builder
Base URL Configuration
< div >
< label className = "block text-[11px] font-medium text-zinc-500 uppercase tracking-wide mb-1" >
Base URL
</ label >
< input
type = "text"
value = { baseUrl }
onChange = { ( e ) => setBaseUrl ( e . target . value ) }
className = "w-full px-3 py-2 rounded-lg bg-zinc-800 border border-zinc-700
text-sm font-mono text-zinc-200"
placeholder = "API base URL"
/>
</ div >
Default Value: API_URL from your app’s config (e.g., https://api.whatdoc.xyz)
Users can change the base URL to test against staging, localhost, or custom environments.
< textarea
value = { headersText }
onChange = { ( e ) => setHeadersText ( e . target . value ) }
rows = { 3 }
spellCheck = { false }
className = "w-full px-3 py-2 rounded-lg bg-zinc-800 border border-zinc-700
text-sm font-mono text-zinc-200 resize-y"
/>
Default Headers:
{
"Content-Type" : "application/json"
}
With Authentication:
{
"Content-Type" : "application/json" ,
"Authorization" : "Bearer YOUR_TOKEN"
}
Invalid JSON in the headers field will display an error message: “Invalid JSON in headers”
Request Body Editor
Only visible for methods that accept a body (POST, PUT, PATCH):
const hasBody = ! [ 'GET' , 'HEAD' , 'DELETE' ]. includes ( methodUpper );
{ hasBody && (
< div >
< label className = "block text-[11px] font-medium text-zinc-500 uppercase tracking-wide mb-1" >
Request Body (JSON)
</ label >
< textarea
value = { bodyText }
onChange = { ( e ) => setBodyText ( e . target . value ) }
rows = { 6 }
spellCheck = { false }
className = "w-full px-3 py-2 rounded-lg bg-zinc-800 border border-zinc-700
text-sm font-mono text-zinc-200 resize-y"
/>
</ div >
)}
Example Body (from AI-generated config):
Sending Requests
The Send Request button triggers the fetch call:
const runRequest = useCallback ( async () => {
setLoading ( true );
setError ( null );
setResponse ( null );
setStatusCode ( null );
try {
let parsedHeaders = {};
try {
parsedHeaders = JSON . parse ( headersText );
} catch {
throw new Error ( 'Invalid JSON in headers' );
}
const fetchOpts = {
method: methodUpper ,
headers: { 'Content-Type' : 'application/json' , ... parsedHeaders },
};
if ( hasBody && bodyText . trim ()) {
try {
JSON . parse ( bodyText ); // validate
} catch {
throw new Error ( 'Invalid JSON in request body' );
}
fetchOpts . body = bodyText ;
}
const url = ` ${ baseUrl . replace ( / \/ + $ / , '' ) }${ endpoint } ` ;
const res = await fetch ( url , fetchOpts );
setStatusCode ( res . status );
const contentType = res . headers . get ( 'content-type' ) || '' ;
if ( contentType . includes ( 'application/json' )) {
const json = await res . json ();
setResponse ( JSON . stringify ( json , null , 2 ));
} else {
const text = await res . text ();
setResponse ( text . slice ( 0 , 5000 )); // Truncate large responses
}
} catch ( err ) {
setError ( err . message || 'Request failed' );
} finally {
setLoading ( false );
}
}, [ baseUrl , headersText , bodyText , methodUpper , hasBody , endpoint ]);
Loading State:
< button onClick = { runRequest } disabled = { loading } >
{ loading ? (
< Loader2 className = "size-4 animate-spin" />
) : (
< Play className = "size-4" />
) }
{ loading ? 'Sending...' : 'Send Request' }
</ button >
Response Viewer
After a successful request, the response appears below:
Status Code Badge
{ statusCode && (
< span className = { `px-2 py-0.5 rounded text-[11px] font-mono font-bold ${
statusCode < 300
? 'bg-emerald-500/15 text-emerald-400'
: statusCode < 500
? 'bg-amber-500/15 text-amber-400'
: 'bg-red-500/15 text-red-400'
} ` } >
{ statusCode }
</ span >
)}
Color Coding:
2xx (Success): Green
3xx-4xx (Client Error): Amber
5xx (Server Error): Red
Response Body
< pre className = "px-4 py-3 rounded-lg bg-zinc-950 border border-zinc-800
text-sm font-mono text-zinc-300 overflow-x-auto
max-h-80 overflow-y-auto whitespace-pre-wrap" >
{ response }
</ pre >
Features:
Syntax-highlighted JSON (if response is JSON)
Copy-to-clipboard button
Scrollable for long responses
Truncated to 5000 characters for non-JSON responses
< button onClick = { copyResponse } className = "flex items-center gap-1 text-xs" >
{ copied ? < Check className = "size-3" /> : < Copy className = "size-3" /> }
{ copied ? 'Copied' : 'Copy' }
</ button >
Error Handling
Errors are displayed in a red alert box:
{ error && (
< div className = "px-4 py-3 rounded-lg bg-red-500/10 border border-red-500/30
text-red-400 text-sm font-mono" >
{ error }
</ div >
)}
Common Errors:
“Invalid JSON in headers”
“Invalid JSON in request body”
“Request failed” (network error, CORS, timeout)
CORS & Security
The playground makes requests directly from the user’s browser . If your API doesn’t allow cross-origin requests, the playground will fail with a CORS error.
Enabling CORS for Documentation Testing
Add your docs domain to your API’s CORS whitelist:
// Express.js example
const cors = require ( 'cors' );
app . use ( cors ({
origin: [
'https://your-subdomain.whatdoc.xyz' ,
'https://docs.your-startup.com'
],
credentials: true
}));
For development, you can temporarily allow all origins: origin: '*'
Playground Configuration Examples
Simple GET Request
{
"method" : "GET" ,
"endpoint" : "/users" ,
"headers" : {
"Authorization" : "Bearer YOUR_TOKEN"
}
}
POST with Complex Body
{
"method" : "POST" ,
"endpoint" : "/projects" ,
"headers" : {
"Content-Type" : "application/json" ,
"Authorization" : "Bearer YOUR_TOKEN"
},
"body" : {
"repoName" : "acme/api" ,
"slug" : "acme-api" ,
"template" : "twilio" ,
"isPublic" : true
}
}
DELETE Request (No Body)
{
"method" : "DELETE" ,
"endpoint" : "/projects/abc123" ,
"headers" : {
"Authorization" : "Bearer YOUR_TOKEN"
}
}
Method Color Codes
The playground uses consistent color coding for HTTP methods:
const METHOD_COLORS = {
GET: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/30' ,
POST: 'bg-blue-500/15 text-blue-400 border-blue-500/30' ,
PUT: 'bg-amber-500/15 text-amber-400 border-amber-500/30' ,
PATCH: 'bg-orange-500/15 text-orange-400 border-orange-500/30' ,
DELETE: 'bg-red-500/15 text-red-400 border-red-500/30' ,
};
Responsive Design
The playground adapts to mobile screens:
Desktop: Full-width with side-by-side headers/body
Tablet: Stacked layout
Mobile: Single-column with larger touch targets
Usage in Generated Docs
When the AI generates documentation for an API endpoint, it includes a playground block:
## POST /auth/signup
Create a new user account.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| email | string | Yes | User's email address |
| password | string | Yes | Password (min 8 chars) |
| username | string | Yes | Unique username |
### Example Request
```bash
curl -X POST https://api.whatdoc.xyz/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected] ",
"password": "securepass123",
"username": "newuser"
}'
```
### Example Response (201 Created)
```json
{
"success" : true ,
"user" : {
"id" : "usr_abc123" ,
"email" : "[email protected] " ,
"username" : "newuser"
},
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
### Try It
```json-api-playground
{
"method": "POST",
"endpoint": "/auth/signup",
"headers": { "Content-Type": "application/json" },
"body": {
"email": "[email protected] ",
"password": "securepass123",
"username": "newuser"
}
}
```
Rendering Logic
The template detects json-api-playground code blocks and renders them as interactive components:
import ReactMarkdown from 'react-markdown' ;
import ApiPlayground from '../components/ApiPlayground' ;
< ReactMarkdown
components = { {
code ({ node , inline , className , children , ... props }) {
const match = /language- ( \w + ) / . exec ( className || '' );
const lang = match ?.[ 1 ];
// Detect API playground blocks
if ( lang === 'json-api-playground' && ! inline ) {
try {
const config = JSON . parse ( String ( children ));
return < ApiPlayground config = { config } /> ;
} catch {
return < pre > Invalid playground config </ pre > ;
}
}
// Regular code block
return < SyntaxHighlighter language = { lang } > { children } </ SyntaxHighlighter > ;
}
} }
>
{ docs }
</ ReactMarkdown >
Bundle size: 8.2 KB (gzipped)
First render: < 50ms
Request latency: Depends on your API
The playground uses native fetch API — no external HTTP libraries required.
Limitations
File uploads: Not supported (use multipart/form-data endpoints require custom handling)
Binary responses: Only JSON and text responses are displayed
Streaming responses: Not supported (SSE/WebSocket endpoints require custom handling)
GraphQL: Not yet supported (coming soon)
Next Steps
AI Generation Learn how the AI engine detects and documents your API
Custom Domains Host your interactive docs on your own domain