From e153a5c64014ae59ee2943ece1eb62a85529f2a5 Mon Sep 17 00:00:00 2001 From: scottjstrand1 Date: Fri, 10 Jul 2026 12:57:32 -0500 Subject: [PATCH] Add cURL examples to API reference markdown exports. Generated API reference .md files now include executable cURL examples for each endpoint, matching the interactive API reference UI. Co-authored-by: Cursor --- components/ui/ApiReference/helpers.ts | 38 +++++++++++++++++++++------ scripts/generateApiMarkdown.ts | 36 ++++++++++++++++++++----- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/components/ui/ApiReference/helpers.ts b/components/ui/ApiReference/helpers.ts index aba4864f3..27ca87552 100644 --- a/components/ui/ApiReference/helpers.ts +++ b/components/ui/ApiReference/helpers.ts @@ -211,6 +211,29 @@ function buildSidebarPages( return pages; } +function generateCurlRequestExample({ + baseUrl, + methodType, + endpoint, + body, +}: { + baseUrl: string; + methodType: string; + endpoint: string; + body?: unknown; +}) { + const maybeBodyString = body ? `-d '${JSON.stringify(body)}'` : ""; + + return [ + `curl -X ${methodType.toUpperCase()} ${baseUrl}${endpoint} \\`, + `-H "Content-Type: application/json" \\`, + `-H "Authorization: Bearer sk_test_12345" \\`, + maybeBodyString, + ] + .filter(Boolean) + .join("\n"); +} + function augmentSnippetsWithCurlRequest( snippets: Record, { @@ -225,15 +248,13 @@ function augmentSnippetsWithCurlRequest( body?: Record; }, ) { - const maybeBodyString = body ? `-d '${JSON.stringify(body)}'` : ""; - return { - curl: ` - curl -X ${methodType.toUpperCase()} ${baseUrl}${endpoint} \\ - -H "Content-Type: application/json" \\ - -H "Authorization: Bearer sk_test_12345" \\ - ${maybeBodyString} - `, + curl: generateCurlRequestExample({ + baseUrl, + methodType, + endpoint, + body, + }), ...snippets, }; } @@ -243,6 +264,7 @@ export { buildSchemaReferences, buildSidebarPages, formatResponseStatusCodes, + generateCurlRequestExample, getSidebarContent, resolveEndpointFromMethod, resolveResponseSchemas, diff --git a/scripts/generateApiMarkdown.ts b/scripts/generateApiMarkdown.ts index d667746af..c3dd9f266 100644 --- a/scripts/generateApiMarkdown.ts +++ b/scripts/generateApiMarkdown.ts @@ -6,7 +6,7 @@ import { unified } from "unified"; import remarkParse from "remark-parse"; import remarkFrontmatter from "remark-frontmatter"; import yaml from "yaml"; -import { resolveEndpointFromMethod } from "../components/ui/ApiReference/helpers"; +import { resolveEndpointFromMethod, generateCurlRequestExample } from "../components/ui/ApiReference/helpers"; import { getAtPointer } from "../lib/jsonPointer"; import { readOpenApiSpec, readStainlessSpec } from "../lib/openApiSpec"; import { @@ -251,6 +251,8 @@ async function generateApiReferenceMarkdownFiles( ); writeMarkdownFile(rootOverviewPath, allOverviewContent); + const baseUrl = stainlessSpec.environments.production; + // Add resource content for (const resourceName of resourceOrder) { const resource = stainlessSpec.resources[resourceName]; @@ -275,6 +277,7 @@ async function generateApiReferenceMarkdownFiles( methodName, method, openApiSpec, + baseUrl, ); combinedContent += methodContent; resourceContent += methodContent; @@ -302,6 +305,7 @@ async function generateApiReferenceMarkdownFiles( subresourceName, subresource, openApiSpec, + baseUrl, ); combinedContent += subresourceContent; resourceContent += subresourceContent; @@ -313,6 +317,7 @@ async function generateApiReferenceMarkdownFiles( [subresourceName], subresource, openApiSpec, + baseUrl, ); } } @@ -375,6 +380,7 @@ function generateSubresourcePages( subresourcePath: string[], subresource: any, openApiSpec: any, + baseUrl: string, ) { // Build the directory path for this subresource const subresourceDir = path.join( @@ -432,6 +438,7 @@ function generateSubresourcePages( methodName, method, openApiSpec, + baseUrl, ); if (methodContent.trim()) { @@ -472,6 +479,7 @@ function generateSubresourcePages( [...subresourcePath, nestedName], nestedSubresource, openApiSpec, + baseUrl, ); } } @@ -516,6 +524,7 @@ function getMethodMarkdownContent( methodName: string, method: any, openApiSpec: any, + baseUrl: string, ): string { const [methodType, endpoint] = resolveEndpointFromMethod( method as string | { endpoint: string }, @@ -534,6 +543,19 @@ function getMethodMarkdownContent( content += `#### Endpoint\n\n`; content += `\`${methodType.toUpperCase()} ${endpoint}\`\n\n`; + const requestBodyContent = + openApiOperation.requestBody?.content?.["application/json"]; + const requestBody = requestBodyContent?.schema; + const requestExample = requestBodyContent?.example || requestBody?.example; + + content += `#### cURL example\n\n`; + content += `\`\`\`bash\n${generateCurlRequestExample({ + baseUrl, + methodType, + endpoint, + body: requestExample, + })}\n\`\`\`\n\n`; + // Add rate limit info if available if (openApiOperation["x-ratelimit-tier"]) { content += `**Rate limit tier:** ${openApiOperation["x-ratelimit-tier"]}\n\n`; @@ -567,15 +589,11 @@ function getMethodMarkdownContent( } // Add request body if present - const requestBodyContent = - openApiOperation.requestBody?.content?.["application/json"]; - const requestBody = requestBodyContent?.schema; if (requestBody) { content += `#### Request body\n\n`; if (requestBody.description) { content += `${requestBody.description}\n\n`; } - const requestExample = requestBodyContent?.example || requestBody.example; if (requestExample) { content += `##### Example\n\n`; content += `\`\`\`json\n${JSON.stringify( @@ -619,6 +637,7 @@ function getSubresourceMarkdownContent( subresourceName: string, subresource: any, openApiSpec: any, + baseUrl: string, ): string { let content = ""; @@ -647,7 +666,12 @@ function getSubresourceMarkdownContent( // Add method content for subresource if (subresource.methods) { for (const [methodName, method] of Object.entries(subresource.methods)) { - content += getMethodMarkdownContent(methodName, method, openApiSpec); + content += getMethodMarkdownContent( + methodName, + method, + openApiSpec, + baseUrl, + ); } }