Auto-generated curl / JS / Python examples are the bare minimum. Your users probably have an SDK. The x-codeSamples OpenAPI extension (a ReDoc convention, widely adopted) lets you attach hand-written or Speakeasy/Stainless-generated SDK examples directly to each operation, and NookDocs merges them into the endpoint code card automatically.
Why this matters
The Try-It code card on every endpoint page ships three auto-generated tabs by default: cURL, JavaScript (fetch), Python (requests). These are fine for exploration but not what your users actually paste into their codebase — they paste SDK calls. x-codeSamples gives you that extra tab strip with zero UI configuration on our side. Drop the extension into your spec, redeploy, done.
The extension
paths:
/payments:
post:
operationId: createPayment
summary: Create a payment
x-codeSamples:
- lang: TypeScript
label: "@acme/sdk"
source: |
import Acme from "@acme/sdk";
const acme = new Acme({ apiKey: process.env.ACME_KEY });
const payment = await acme.payments.create({
amount: 1000,
currency: "usd",
source: "tok_visa",
});
- lang: Python
label: "acme-python"
source: |
from acme import Acme
acme = Acme(api_key="sk_...")
payment = acme.payments.create(
amount=1000,
currency="usd",
source="tok_visa",
)
- lang: Go
label: "acme-go"
source: |
acme := acme.New("sk_...")
payment, err := acme.Payments.Create(&acme.PaymentCreateParams{
Amount: acme.Int64(1000),
Currency: acme.String("usd"),
Source: acme.String("tok_visa"),
})Each entry becomes a tab in the endpoint's code card. Tab labels come from label (fallback: lang). The tab content is the source string verbatim, syntax-highlighted via Prism based on lang.
Field reference
x-codeSamples[].langstringpathrequiredLanguage slug. Controls syntax highlighting. Common values: typescript, javascript, python, ruby, go, rust, java, php, csharp, swift, kotlin, bash, curl. Free-form — unknown slugs render as plain text without highlighting.
x-codeSamples[].labelstringpathHuman-readable tab label. Falls back to lang when omitted. Use this to differentiate multiple entries of the same language (e.g. "SDK" vs "REST (fetch)" for two TypeScript samples).
x-codeSamples[].sourcestringpathrequiredThe code snippet. Multiline strings via YAML | block scalar are the cleanest — indentation is preserved exactly. Keep it under ~50 lines per tab so readers can scan without scrolling the whole page.
How NookDocs merges these
The endpoint code card renders tabs in this order:
Auto-generated — cURL + JS fetch + Python requests, synthesised from the spec.
x-codeSamples— appended after, in the order declared in the spec.
If x-codeSamples contains a language that conflicts with an auto-generated tab (e.g. you provided your own curl), your version wins — the auto-gen is dropped. This gives you full control when you need it.
Reader clicks a tab → the Try-It playground uses the currently visible tab's language as the base for its "Run request" call where applicable (for cURL, fetch, requests — SDK snippets are display-only).
Generator tools
You don't have to hand-write these. Commercial and open-source tools generate x-codeSamples entries from your OpenAPI spec automatically:
Hand-written
Totally fine for small specs. Pay attention to authentication setup — readers will copy-paste, so make the snippets runnable after one env-var swap.
Authoring guidance
Keep them paste-runnable
The fastest path from "read the docs" to "first 200 response" is a snippet the reader can paste, swap the API key, and run. Include the import line. Include the env-var lookup. Omit everything else — no explanatory comments, no try/catch unless the point is the try/catch.
// Good — copy, paste, swap key, run
import Acme from "@acme/sdk";
const acme = new Acme({ apiKey: process.env.ACME_KEY });
const payment = await acme.payments.create({ amount: 1000, currency: "usd" });// Bad — too much scaffolding, reader stops reading halfway through
async function createPaymentExample() {
try {
// First, initialize the SDK with your API key from env
const acme = new Acme({
apiKey: process.env.ACME_KEY ?? "fallback-value",
});
// Now call the payments.create method...
const payment = await acme.payments.create({
amount: 1000, // in cents
currency: "usd",
/* more options here */
});
console.log("Created payment", payment.id);
} catch (err) {
console.error("Payment failed:", err);
}
}One variant per use case
Don't ship 6 TypeScript samples for the same endpoint. Pick one canonical example per SDK, per operation. Use the label field to differentiate if you truly need two (e.g. "SDK (async)" vs "SDK (sync)").
Keep labels short
Tab strips break layout when labels are long. Target ≤12 chars. "@acme/sdk" beats "Acme TypeScript SDK (npm package)".
Order by user preference
Tabs render in declared order. Put your users' most likely language first — for B2B SaaS that's usually TypeScript or Python. Not cURL — cURL is already auto-generated.
Inspect the merged output
Every endpoint page serves the agent-filtered MDX at /<endpoint-path>.md. The x-codeSamples entries appear there too so LLMs helping users generate integration code can see what canonical SDK usage looks like:
curl -sL https://docs.acme.com/api-reference/payments/create-payment.mdThe Copy-for-LLMs button in the endpoint page header emits the same view.
Limitations
No inline execution. Unlike cURL / fetch / requests tabs, SDK snippets are display-only. The Try-It playground doesn't execute arbitrary SDK calls.
No syntax validation. We render whatever
sourcestring you provide. Broken snippets ship as broken snippets.No Speakeasy/Stainless-specific field names. Both tools emit standard
x-codeSamples. If your generator uses a proprietary extension (e.g.x-speakeasy-code-samplesor similar), rename tox-codeSamplesor configure the generator to output the standard form.
Related
Playground — the Try-It code card where these tabs render
Manual API pages — escape hatch with
<RequestExample>/<ResponseExample>componentsOpenAPI overview — how specs wire up
Complex types —
oneOf/anyOf/allOfhandling