customize-sdk-runtime
customize-sdk-runtime
Configure runtime behavior for Speakeasy-generated SDKs including retries, timeouts, pagination, server selection, custom code preservation, and error handling.
When to Use
- Configuring retry logic for SDK operations
- Setting global or per-operation timeouts
- Adding pagination support to list endpoints
- Selecting between multiple server environments
- Preserving custom code across SDK regeneration
- Customizing error handling and typed error responses
- User says: "SDK retries", "timeout configuration", "server selection", "pagination config", "custom code", "error handling"
Inputs
| Input | Required | Description |
|---|---|---|
| OpenAPI spec | Yes | Path to the OpenAPI spec to configure |
| gen.yaml | Sometimes | Generation config for error handling customization |
| Target language | Helpful | TypeScript, Python, or Go (defaults vary) |
Outputs
| Output | Description |
|---|---|
| Updated OpenAPI spec | Spec with runtime extensions applied |
| SDK configuration | Runtime behavior configured in generated SDK |
| Custom code files | Hook files preserved across regeneration |
Prerequisites
- Speakeasy CLI installed and authenticated
- An existing OpenAPI spec
- An existing SDK project (for runtime override examples)
Command
Runtime configuration is primarily done through OpenAPI extensions, not CLI commands. After modifying the spec, regenerate:
speakeasy run --output console
Retry Configuration
Global Retries (OpenAPI Spec)
Add x-speakeasy-retries at the root of your OpenAPI document:
openapi: 3.1.0
info:
title: My API
version: 1.0.0
x-speakeasy-retries:
strategy: backoff
backoff:
initialInterval: 500 # milliseconds
maxInterval: 60000 # milliseconds
maxElapsedTime: 3600000 # milliseconds (1 hour)
exponent: 1.5
statusCodes:
- 5XX
- 408
- 429
retryConnectionErrors: true
Per-Operation Retries
Override retries on a specific operation by adding x-speakeasy-retries at the operation level with the same schema as above.
Retry Options Reference
| Option | Type | Description |
|---|---|---|
strategy |
string | Must be backoff |
backoff.initialInterval |
integer | First retry delay in ms |
backoff.maxInterval |
integer | Maximum delay between retries in ms |
backoff.maxElapsedTime |
integer | Total time limit for all retries in ms |
backoff.exponent |
number | Backoff multiplier (e.g., 1.5, 2.0) |
statusCodes |
string[] | HTTP status codes to retry (supports 5XX patterns) |
retryConnectionErrors |
boolean | Retry on connection failures |
Runtime Override (TypeScript)
const res = await sdk.payments.create(
{ amount: 1000 },
{
retries: {
strategy: "backoff",
backoff: {
initialInterval: 1000,
maxInterval: 30000,
maxElapsedTime: 300000,
exponent: 2.0,
},
retryConnectionErrors: true,
},
}
);
Runtime Override (Python)
from sdk.utils import BackoffStrategy, RetryConfig
res = sdk.payments.create(
amount=1000,
retries=RetryConfig("backoff",
backoff=BackoffStrategy(1000, 30000, 300000, 2.0),
retry_connection_errors=True),
)
Runtime Override (Go)
res, err := sdk.Payments.Create(ctx, req, operations.WithRetries(retry.Config{
Strategy: "backoff",
Backoff: &retry.BackoffStrategy{
InitialInterval: 1000, MaxInterval: 30000,
MaxElapsedTime: 300000, Exponent: 2.0,
},
RetryConnectionErrors: true,
}))
Timeout Configuration
Global Timeout (OpenAPI Spec)
Set a default timeout in milliseconds at the document root:
x-speakeasy-timeout: 30000 # 30 seconds
Per-Operation Timeout
Override timeout on specific operations:
paths:
/reports/generate:
post:
operationId: generateReport
x-speakeasy-timeout: 120000 # 2 minutes for long-running ops
Runtime Override
TypeScript:
const sdk = new SDK({ timeoutMs: 30000 }); // global
const res = await sdk.reports.generate({ type: "annual" }, { timeoutMs: 120000 }); // per-call
Python:
sdk = SDK(timeout_ms=30000) # global
res = sdk.reports.generate(type="annual", timeout_ms=120000) # per-call
Go:
sdk := SDK.New(SDK.WithTimeoutMs(30000)) // global
res, err := sdk.Reports.Generate(ctx, req, operations.WithTimeoutMs(120000)) // per-call
Pagination Configuration
Add x-speakeasy-pagination to list endpoints:
paths:
/users:
get:
operationId: listUsers
x-speakeasy-pagination:
type: offsetLimit
inputs:
- name: offset
in: parameters
type: offset
- name: limit
in: parameters
type: limit
outputs:
results: $.data
Pagination Types
| Type | Description | Use Case |
|---|---|---|
offsetLimit |
Offset + limit parameters | Traditional pagination |
cursor |
Cursor-based navigation | Large datasets, real-time data |
url |
Next-page URL in response | HATEOAS-style APIs |
For cursor type, use type: cursor in inputs and add nextCursor: $.meta.next_cursor to outputs.
SDK Usage (TypeScript)
// Auto-iterate through all pages
for await (const user of await sdk.users.list({ limit: 50 })) {
console.log(user.name);
}
// Or manually page with next()
let page = await sdk.users.list({ limit: 50 });
while (page) {
for (const user of page.data) { console.log(user.name); }
page = await page.next();
}
Server Configuration
Multiple Servers in Spec
servers:
- url: https://api.example.com
description: Production
x-speakeasy-server-id: production
- url: https://sandbox.example.com
description: Sandbox
x-speakeasy-server-id: sandbox
Runtime Server Selection
TypeScript:
const sdk = new SDK({ server: "sandbox" });
// or custom URL:
const sdk = new SDK({ serverURL: "https://custom.example.com" });
Python: sdk = SDK(server="sandbox") or sdk = SDK(server_url="...")
Go: sdk := SDK.New(SDK.WithServer("sandbox")) or SDK.WithServerURL("...")
Custom Code Preservation
Files in src/hooks/ (TypeScript) or the equivalent hooks directory are preserved during SDK regeneration.
Key Rules
registration.tsis generated on the first run, then never overwritten- Any new files you add to
src/hooks/are preserved across regeneration - Register custom hooks in
registration.tsto integrate them with the SDK lifecycle
Example: Custom Hook Registration
// src/hooks/registration.ts
import { Hooks } from "./types";
import { initLoggingHook } from "./logging";
import { initCustomAuthHook } from "./custom-auth";
export function initHooks(hooks: Hooks) {
initLoggingHook(hooks);
initCustomAuthHook(hooks);
}
Error Handling
Custom Error Schemas (gen.yaml)
generation:
errors:
statusCodes:
- statusCode: "4XX"
schema: "#/components/schemas/ClientError"
- statusCode: "5XX"
schema: "#/components/schemas/ServerError"
Define typed error responses in your OpenAPI spec by adding response schemas for specific status codes (e.g., 404, 422). The SDK generates typed error classes for each.
SDK Error Handling (TypeScript)
import { NotFoundError, ValidationError } from "./sdk/models/errors";
try {
const user = await sdk.users.get({ id: "123" });
} catch (err) {
if (err instanceof NotFoundError) {
console.log("User not found:", err.message);
} else if (err instanceof ValidationError) {
console.log("Validation failed:", err.errors);
} else {
throw err;
}
}
Error Transform Hooks
Add custom error transformation via hooks:
// src/hooks/error-transform.ts
export function initErrorTransformHook(hooks: Hooks) {
hooks.registerAfterError((_hookCtx, response, error) => {
if (error) { console.error("API error:", error.message); }
return { response, error };
});
}
Example
Scenario: Configure retries, a 30-second global timeout, and sandbox server selection.
- Add extensions to your OpenAPI spec:
openapi: 3.1.0
info:
title: Payments API
version: 1.0.0
x-speakeasy-retries:
strategy: backoff
backoff:
initialInterval: 500
maxInterval: 60000
maxElapsedTime: 300000
exponent: 1.5
statusCodes: [5XX, 429]
retryConnectionErrors: true
x-speakeasy-timeout: 30000
servers:
- url: https://api.payments.com
x-speakeasy-server-id: production
- url: https://sandbox.payments.com
x-speakeasy-server-id: sandbox
-
Regenerate:
speakeasy run --output console -
Use with runtime overrides:
const sdk = new SDK({ server: "sandbox", timeoutMs: 30000 });
const payment = await sdk.payments.create(
{ amount: 5000, currency: "usd" },
{
retries: {
strategy: "backoff",
backoff: { initialInterval: 2000, maxInterval: 30000,
maxElapsedTime: 120000, exponent: 2.0 },
},
timeoutMs: 60000,
}
);
What NOT to Do
- Do NOT set excessively long
maxElapsedTimevalues that could hang requests indefinitely - Do NOT retry on 4XX status codes (except 408 and 429) as they indicate client errors
- Do NOT modify generated files outside of
src/hooks/as they will be overwritten on regeneration - Do NOT set timeouts to 0 expecting "no timeout" -- omit the extension instead
- Do NOT hardcode server URLs in application code -- use
x-speakeasy-server-idand select at runtime
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Retries not working | Extension at wrong level | Ensure x-speakeasy-retries is at document root or operation level |
| Timeout not applying | Value not in milliseconds | x-speakeasy-timeout expects milliseconds, not seconds |
Pagination next() undefined |
Missing extension | Add x-speakeasy-pagination to the list operation |
| Custom hooks lost on regen | Files outside hooks dir | Move custom code to src/hooks/ directory |
| Server ID not recognized | Missing extension | Add x-speakeasy-server-id to each server entry |
| Typed errors not generated | Missing error schemas | Define error response schemas per status code in OpenAPI spec |
| Hook not executing | Not registered | Register your hook in registration.ts |