@@ -210,6 +210,35 @@ export interface LoadOpenAPIOptions {
210210 headers ?: Record < string , string > ;
211211 body ?: string | undefined ;
212212 } ;
213+
214+ /**
215+ * Custom fetch function for full control over the HTTP transport layer.
216+ * Use this when the target API requires custom TLS certificates (mTLS, custom CA),
217+ * a proxy, or any other transport-level configuration that headers alone cannot provide.
218+ *
219+ * The function receives the URL and standard RequestInit on every request,
220+ * making it inherently dynamic — you can route to different agents based on the URL.
221+ *
222+ * Applied to both spec loading and runtime API calls.
223+ *
224+ * @example
225+ * ```typescript
226+ * import { Agent } from 'undici';
227+ *
228+ * const mtlsAgent = new Agent({
229+ * connect: {
230+ * ca: fs.readFileSync('/certs/ca.pem'),
231+ * cert: fs.readFileSync('/certs/client.pem'),
232+ * key: fs.readFileSync('/certs/client-key.pem'),
233+ * },
234+ * });
235+ *
236+ * await server.loadOpenAPI('https://partner-api.example.com/spec.json', {
237+ * fetcher: (url, init) => fetch(url, { ...init, dispatcher: mtlsAgent }),
238+ * });
239+ * ```
240+ */
241+ fetcher ?: ( url : string , init : RequestInit ) => Promise < Response > ;
213242}
214243
215244/**
@@ -233,7 +262,7 @@ export async function loadOpenAPI(
233262 source : string ,
234263 options : LoadOpenAPIOptions = { }
235264) : Promise < APIGroupConfig > {
236- const spec = await loadSpec ( source ) ;
265+ const spec = await loadSpec ( source , options . fetcher ) ;
237266
238267 const name = options . name || spec . info . title . toLowerCase ( ) . replace ( / \s + / g, '-' ) ;
239268
@@ -297,12 +326,16 @@ export async function loadOpenAPI(
297326/**
298327 * Load OpenAPI spec from file or URL
299328 */
300- async function loadSpec ( source : string ) : Promise < APISpec > {
329+ async function loadSpec (
330+ source : string ,
331+ fetcher ?: ( url : string , init : RequestInit ) => Promise < Response >
332+ ) : Promise < APISpec > {
301333 let content : string ;
302334 let isYaml = false ;
303335
304336 if ( source . startsWith ( 'http://' ) || source . startsWith ( 'https://' ) ) {
305- const response = await fetch ( source ) ;
337+ const fetchFn = fetcher || fetch ;
338+ const response = await fetchFn ( source , { method : 'GET' } ) ;
306339 if ( ! response . ok ) {
307340 throw new Error ( `Failed to load OpenAPI spec from ${ source } : ${ response . statusText } ` ) ;
308341 }
@@ -625,7 +658,8 @@ function convertOperation(
625658 if ( transformed . body !== undefined ) finalBody = transformed . body ;
626659 }
627660
628- const response = await fetch ( finalUrl , {
661+ const fetchFn = options . fetcher || fetch ;
662+ const response = await fetchFn ( finalUrl , {
629663 method : finalMethod ,
630664 headers : finalHeaders ,
631665 body : finalBody ,
0 commit comments