Claude writes better API documentation than most humans do — here's the prompt
← Back
April 4, 2026Claude5 min read

Claude writes better API documentation than most humans do — here's the prompt

Published April 4, 20265 min read

I was reviewing a new SDK we were releasing and the documentation was sparse — function signatures, a one-line description, no examples, no error details. I pasted three functions into Claude with a specific documentation prompt and got back complete JSDoc blocks with examples, parameter descriptions, thrown errors, and edge case notes. It took less time than writing one good doc manually.

The documentation prompt

python
import anthropic

client = anthropic.Anthropic()

DOCS_SYSTEM = """You are a technical writer documenting a TypeScript/JavaScript SDK.

For each function, generate complete JSDoc documentation that includes:
- @description — what it does (2-3 sentences, explain the why not just the what)
- @param — each parameter with type, description, whether required, and valid values/range
- @returns — what it returns and its shape
- @throws — every possible error with when it is thrown
- @example — a realistic usage example (not just 'foo(bar)')
- @remarks — edge cases, performance notes, or important caveats

Write for a developer who has never seen this codebase. Be specific about behavior.
Do not use vague language like "handles", "manages", "deals with"."""


def document_function(function_code: str) -> str:
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1000,
        system=DOCS_SYSTEM,
        messages=[{
            "role": "user",
            "content": f"Generate complete JSDoc for this function:

{function_code}"
        }]
    )
    return response.content[0].text

Example: before and after

Input function:

typescript
async function uploadFile(
  file: Buffer | ReadableStream,
  options?: {
    filename?: string;
    mimeType?: string;
    maxSizeBytes?: number;
    onProgress?: (bytesUploaded: number, total: number) => void;
  }
): Promise

Claude's output:

typescript
/**
 * Uploads a file to the storage service and returns a URL and metadata.
 * The file is uploaded in chunks to support large files and progress tracking.
 * If the upload fails partway, the partial upload is automatically cleaned up.
 *
 * @param file - The file content to upload. Accepts either a complete Buffer
 *   for in-memory files, or a ReadableStream for large files where loading
 *   the entire file into memory is not practical.
 *
 * @param options - Optional upload configuration.
 * @param options.filename - The filename to store. If omitted, a UUID is generated.
 *   Affects the `Content-Disposition` header on download but not the storage key.
 * @param options.mimeType - MIME type of the file (e.g., 'image/jpeg', 'application/pdf').
 *   If omitted, the service attempts auto-detection. Incorrect MIME types may cause
 *   issues with some browsers when serving the file.
 * @param options.maxSizeBytes - Maximum allowed file size in bytes. Defaults to
 *   the account limit (usually 100MB). Upload fails immediately if exceeded.
 * @param options.onProgress - Callback invoked after each chunk is uploaded.
 *   `bytesUploaded` is cumulative. `total` is -1 for ReadableStream inputs
 *   where size is unknown in advance.
 *
 * @returns A Promise that resolves to an UploadResult containing:
 *   - `url`: The publicly accessible URL (valid for 7 days by default)
 *   - `key`: The storage key for subsequent operations (permanent)
 *   - `size`: Actual file size in bytes
 *   - `mimeType`: The resolved MIME type
 *
 * @throws {FileSizeLimitError} If the file exceeds `maxSizeBytes` or the account limit.
 * @throws {InvalidMimeTypeError} If `mimeType` is specified but not in the allowed list.
 * @throws {NetworkError} If the upload fails due to a network interruption.
 *   The partial upload is cleaned up before this error is thrown.
 * @throws {AuthenticationError} If the API key is invalid or expired.
 *
 * @example
 * // Upload an image from disk with progress tracking
 * import { createReadStream } from 'fs';
 *
 * const stream = createReadStream('./profile.jpg');
 * const result = await uploadFile(stream, {
 *   filename: 'profile.jpg',
 *   mimeType: 'image/jpeg',
 *   onProgress: (uploaded, total) => {
 *     if (total > 0) console.log(`${Math.round(uploaded / total * 100)}%`);
 *   }
 * });
 * console.log(result.url); // https://cdn.example.com/uploads/...
 *
 * @remarks
 * ReadableStream inputs are consumed and cannot be re-uploaded without
 * recreating the stream. Buffer inputs can be uploaded multiple times.
 * For files over 10MB, prefer ReadableStream to avoid memory pressure.
 */ 

Batch documentation script

bash
# Document all exported functions in a TypeScript file
doc-file() {
  local file="$1"
  
  # Extract each exported function/class and document it
  cat "$file" | claude --print     --system "You are a technical writer. Generate complete JSDoc for every exported function and class. Follow the full JSDoc spec with @param, @returns, @throws, and @example."     "Add complete JSDoc comments to all exported items in this TypeScript file:"
}

The difference good docs make

Before: developers would open the source file every time they used the function to understand the exact behavior. After: hover over the function name in VS Code and get everything they need without leaving their file.

The secret to getting good output is giving Claude the purpose and audience in the system prompt. "Write for a developer who has never seen this codebase" produces different (better) documentation than leaving Claude to infer the audience.

Share this
← All Posts5 min read