Managing Large Example Payloads in API Specs
A 600 KB bulk-import example inlined into your spec looks harmless in review, then it slows every portal page that touches that operation, pushes CI past the Node heap limit, and turns one-line diffs into thousand-line merge conflicts. The fix is to treat large examples as data files, not spec content. This task guide is part of Example Payload Management within the larger OpenAPI & AsyncAPI Schema Authoring workflow, and it covers externalization, CI size limits, lazy rendering, and CDN caching with the exact commands and config.
Problem & Context
OpenAPI and AsyncAPI both let you inline an example or examples object anywhere a payload is described. That is convenient at small sizes and corrosive at large ones. A spec packed with multi-hundred-kilobyte examples runs into three concrete failures:
- Portal rendering slows down. Tools like Redoc and Swagger UI parse and syntax-highlight every example up front, so a handful of large payloads stalls first paint.
- CI runs out of memory. Parsing a spec with dozens of large inline examples exhausts the default Node.js heap and the linter crashes before it reports anything.
- Diffs become unreviewable. A one-field schema change buried under a regenerated 500-row example produces a thousand-line diff and constant merge conflicts.
The target is simple: keep inline examples under about 50 KB, push anything larger into external files referenced by $ref, enforce that boundary in CI, and make the portal and CDN handle the externalized files efficiently.
Step-by-Step Solution
1. Externalize large payloads via $ref
OpenAPIβs examples object supports $ref pointers to external files, which keeps the primary spec small and lets the bundler resolve references at build time:
# openapi.yaml (OpenAPI 3.1.0)
paths:
/orders/bulk:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BulkOrderRequest'
examples:
bulkImport:
summary: 500-row bulk import payload
$ref: './examples/bulk_order_v2.json'
Validate locally right after adding the reference:
npx @redocly/cli lint openapi.yaml --extends recommended
Expected output:
Woohoo! Your API description is valid. π
External references resolve at lint and bundle time. Keep the primary spec under 2 MB and bundle the complete artifact for delivery.
2. Enforce an inline size limit with Spectral
The pattern Spectral function can flag any inline example whose string is longer than 50,000 characters:
# .spectral.yaml
rules:
no-large-inline-examples:
description: Inline examples must not exceed 50 KB (approx. 50,000 characters)
severity: error
given: "$.paths[*][*].requestBody.content[*].example"
then:
function: pattern
functionOptions:
notMatch: ".{50000,}"
Run it:
npx @stoplight/spectral-cli lint openapi.yaml --ruleset .spectral.yaml --format github-actions
Expected output when the rule fires:
/openapi.yaml
45:12 error no-large-inline-examples Inline examples must not exceed 50 KB
3. Run the size check in CI with extra heap
Give the linter enough memory for any examples that remain inline:
# .github/workflows/validate-spec.yml
name: Validate Spec
on: [pull_request]
jobs:
validate-spec:
runs-on: ubuntu-latest
env:
NODE_OPTIONS: '--max-old-space-size=4096'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx @stoplight/spectral-cli lint openapi.yaml --ruleset .spectral.yaml --format github-actions
4. Lazy-load heavy examples in the portal
Defer parsing of large examples until the user expands an operation. Redoc supports lazy-rendering for off-screen operations:
<redoc
spec-url="/openapi.yaml"
lazy-rendering
expand-responses="200,201"
></redoc>
For custom React or Vue portals, fetch the external example on demand rather than at page load:
// Fetch an external example only when the user requests it
async function loadExample(externalRefUrl) {
const response = await fetch(externalRefUrl, {
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`Failed to load example: ${response.status}`);
}
return response.json();
}
5. Cache external example files on the CDN
Examples are versioned by path, so stale-content risk is low. Serve them with a long max-age, a short stale-while-revalidate, and a permissive CORS header:
# nginx.conf
location /examples/ {
add_header Cache-Control "public, max-age=86400, stale-while-revalidate=3600";
add_header Content-Type "application/json";
add_header Access-Control-Allow-Origin "*";
}
Access-Control-Allow-Origin: * is required when the portal domain differs from the domain serving the example files β browsers block cross-origin fetches without it.
Complete Working Example
A self-contained externalization layout you can drop into a repository:
api/
βββ openapi.yaml
βββ examples/
βββ bulk_order_v2.json
.spectral.yaml
.github/workflows/validate-spec.yml
api/openapi.yaml:
openapi: 3.1.0
info:
title: Orders API
version: 1.0.0
paths:
/orders/bulk:
post:
operationId: createBulkOrders
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BulkOrderRequest'
examples:
bulkImport:
summary: 500-row bulk import payload
$ref: './examples/bulk_order_v2.json'
responses:
'202':
description: Accepted for processing
components:
schemas:
BulkOrderRequest:
type: object
required: [orders]
properties:
orders:
type: array
items:
type: object
required: [sku, quantity]
properties:
sku: { type: string }
quantity: { type: integer, minimum: 1 }
api/examples/bulk_order_v2.json holds the large data (plain JSON, no $ref back to the spec):
{
"orders": [
{ "sku": "ABC-001", "quantity": 12 },
{ "sku": "ABC-002", "quantity": 3 }
]
}
Bundle and confirm the reference resolves before publishing:
npx @redocly/cli bundle api/openapi.yaml -o dist/openapi.bundled.yaml
Expected output:
bundling api/openapi.yaml...
π¦ Created a bundle for api/openapi.yaml at dist/openapi.bundled.yaml
Gotchas & Edge Cases
Circular references inside external example files. External JSON that contains $ref or $id pointers back into the main spec causes infinite loops in JSON Schema resolvers. Keep example files as plain data with no schema references β recursion belongs in the schema, not the example, as covered in JSON Schema composition best practices.
Relative path resolution breaks in CI. Git submodules and monorepo workspaces can change the working directory, so ./examples/... may not resolve where you expect. Always write paths relative to the spec fileβs location and verify with @redocly/cli bundle in CI rather than assuming local success carries over.
Missing CORS headers on runtime-fetched examples. If the portal fetches external examples at runtime via JavaScript, the CDN must return a permissive Access-Control-Allow-Origin header and Content-Type: application/json; otherwise the browser silently blocks the request and the example pane stays empty.
FAQ
What is the recommended maximum size for inline examples?
Keep inline examples under approximately 50 KB. Larger payloads should be externalized via $ref to prevent parser timeouts and portal rendering degradation, and a Spectral rule should enforce the limit in CI.
How do I validate external example files against their parent schema?
Run ajv-cli directly against the bundled schema and the example file:
npx ajv validate -s dist/schemas/BulkOrderRequest.json -d examples/bulk_order_v2.json --all-errors
Or bundle with @redocly/cli β when the spec is bundled, Redocly resolves $ref examples and validates their values against the parent schema.
Can AsyncAPI 3.0 handle streaming payloads in examples?
Examples in AsyncAPI represent discrete message frames, not streaming byte sequences. For streaming protocols like Kafka, show a representative single-message frame in the example and split large examples into one file per message type rather than trying to represent a full stream.