@@ -187,6 +187,10 @@ func (s *ClassificationAPIServer) setupRoutes() *http.ServeMux {
187187	// API discovery endpoint 
188188	mux .HandleFunc ("GET /api/v1" , s .handleAPIOverview )
189189
190+ 	// OpenAPI and documentation endpoints 
191+ 	mux .HandleFunc ("GET /openapi.json" , s .handleOpenAPISpec )
192+ 	mux .HandleFunc ("GET /docs" , s .handleSwaggerUI )
193+ 
190194	// Classification endpoints 
191195	mux .HandleFunc ("POST /api/v1/classify/intent" , s .handleIntentClassification )
192196	mux .HandleFunc ("POST /api/v1/classify/pii" , s .handlePIIDetection )
@@ -261,6 +265,8 @@ type EndpointMetadata struct {
261265var  endpointRegistry  =  []EndpointMetadata {
262266	{Path : "/health" , Method : "GET" , Description : "Health check endpoint" },
263267	{Path : "/api/v1" , Method : "GET" , Description : "API discovery and documentation" },
268+ 	{Path : "/openapi.json" , Method : "GET" , Description : "OpenAPI 3.0 specification" },
269+ 	{Path : "/docs" , Method : "GET" , Description : "Interactive Swagger UI documentation" },
264270	{Path : "/api/v1/classify/intent" , Method : "POST" , Description : "Classify user queries into routing categories" },
265271	{Path : "/api/v1/classify/pii" , Method : "POST" , Description : "Detect personally identifiable information in text" },
266272	{Path : "/api/v1/classify/security" , Method : "POST" , Description : "Detect jailbreak attempts and security threats" },
@@ -284,6 +290,78 @@ var taskTypeRegistry = []TaskTypeInfo{
284290	{Name : "all" , Description : "All classification types combined" },
285291}
286292
293+ // OpenAPI 3.0 spec structures 
294+ 
295+ // OpenAPISpec represents an OpenAPI 3.0 specification 
296+ type  OpenAPISpec  struct  {
297+ 	OpenAPI     string                  `json:"openapi"` 
298+ 	Info        OpenAPIInfo             `json:"info"` 
299+ 	Servers     []OpenAPIServer         `json:"servers"` 
300+ 	Paths       map [string ]OpenAPIPath  `json:"paths"` 
301+ 	Components  OpenAPIComponents       `json:"components,omitempty"` 
302+ }
303+ 
304+ // OpenAPIInfo contains API metadata 
305+ type  OpenAPIInfo  struct  {
306+ 	Title        string  `json:"title"` 
307+ 	Description  string  `json:"description"` 
308+ 	Version      string  `json:"version"` 
309+ }
310+ 
311+ // OpenAPIServer describes a server 
312+ type  OpenAPIServer  struct  {
313+ 	URL          string  `json:"url"` 
314+ 	Description  string  `json:"description"` 
315+ }
316+ 
317+ // OpenAPIPath represents operations for a path 
318+ type  OpenAPIPath  struct  {
319+ 	Get     * OpenAPIOperation  `json:"get,omitempty"` 
320+ 	Post    * OpenAPIOperation  `json:"post,omitempty"` 
321+ 	Put     * OpenAPIOperation  `json:"put,omitempty"` 
322+ 	Delete  * OpenAPIOperation  `json:"delete,omitempty"` 
323+ }
324+ 
325+ // OpenAPIOperation describes an API operation 
326+ type  OpenAPIOperation  struct  {
327+ 	Summary      string                      `json:"summary"` 
328+ 	Description  string                      `json:"description,omitempty"` 
329+ 	OperationID  string                      `json:"operationId,omitempty"` 
330+ 	Responses    map [string ]OpenAPIResponse  `json:"responses"` 
331+ 	RequestBody  * OpenAPIRequestBody         `json:"requestBody,omitempty"` 
332+ }
333+ 
334+ // OpenAPIResponse describes a response 
335+ type  OpenAPIResponse  struct  {
336+ 	Description  string                   `json:"description"` 
337+ 	Content      map [string ]OpenAPIMedia  `json:"content,omitempty"` 
338+ }
339+ 
340+ // OpenAPIRequestBody describes a request body 
341+ type  OpenAPIRequestBody  struct  {
342+ 	Description  string                   `json:"description,omitempty"` 
343+ 	Required     bool                     `json:"required,omitempty"` 
344+ 	Content      map [string ]OpenAPIMedia  `json:"content"` 
345+ }
346+ 
347+ // OpenAPIMedia describes media type content 
348+ type  OpenAPIMedia  struct  {
349+ 	Schema  * OpenAPISchema  `json:"schema,omitempty"` 
350+ }
351+ 
352+ // OpenAPISchema describes a schema 
353+ type  OpenAPISchema  struct  {
354+ 	Type        string                    `json:"type,omitempty"` 
355+ 	Properties  map [string ]OpenAPISchema  `json:"properties,omitempty"` 
356+ 	Items       * OpenAPISchema            `json:"items,omitempty"` 
357+ 	Ref         string                    `json:"$ref,omitempty"` 
358+ }
359+ 
360+ // OpenAPIComponents contains reusable components 
361+ type  OpenAPIComponents  struct  {
362+ 	Schemas  map [string ]OpenAPISchema  `json:"schemas,omitempty"` 
363+ }
364+ 
287365// handleAPIOverview handles GET /api/v1 for API discovery 
288366func  (s  * ClassificationAPIServer ) handleAPIOverview (w  http.ResponseWriter , r  * http.Request ) {
289367	// Build endpoints list from registry, filtering out disabled endpoints 
@@ -308,6 +386,8 @@ func (s *ClassificationAPIServer) handleAPIOverview(w http.ResponseWriter, r *ht
308386		TaskTypes :   taskTypeRegistry ,
309387		Links : map [string ]string {
310388			"documentation" : "https://vllm-project.github.io/semantic-router/" ,
389+ 			"openapi_spec" :  "/openapi.json" ,
390+ 			"swagger_ui" :    "/docs" ,
311391			"models_info" :   "/info/models" ,
312392			"health" :        "/health" ,
313393		},
@@ -316,6 +396,158 @@ func (s *ClassificationAPIServer) handleAPIOverview(w http.ResponseWriter, r *ht
316396	s .writeJSONResponse (w , http .StatusOK , response )
317397}
318398
399+ // generateOpenAPISpec generates an OpenAPI 3.0 specification from the endpoint registry 
400+ func  (s  * ClassificationAPIServer ) generateOpenAPISpec () OpenAPISpec  {
401+ 	spec  :=  OpenAPISpec {
402+ 		OpenAPI : "3.0.0" ,
403+ 		Info : OpenAPIInfo {
404+ 			Title :       "Semantic Router Classification API" ,
405+ 			Description : "API for intent classification, PII detection, and security analysis" ,
406+ 			Version :     "v1" ,
407+ 		},
408+ 		Servers : []OpenAPIServer {
409+ 			{
410+ 				URL :         "/" ,
411+ 				Description : "Classification API Server" ,
412+ 			},
413+ 		},
414+ 		Paths : make (map [string ]OpenAPIPath ),
415+ 	}
416+ 
417+ 	// Generate paths from endpoint registry 
418+ 	for  _ , endpoint  :=  range  endpointRegistry  {
419+ 		// Filter out system prompt endpoints if they are disabled 
420+ 		if  ! s .enableSystemPromptAPI  &&  endpoint .Path  ==  "/config/system-prompts"  {
421+ 			continue 
422+ 		}
423+ 
424+ 		path , ok  :=  spec .Paths [endpoint .Path ]
425+ 		if  ! ok  {
426+ 			path  =  OpenAPIPath {}
427+ 		}
428+ 
429+ 		operation  :=  & OpenAPIOperation {
430+ 			Summary :     endpoint .Description ,
431+ 			Description : endpoint .Description ,
432+ 			OperationID : fmt .Sprintf ("%s_%s" , endpoint .Method , endpoint .Path ),
433+ 			Responses : map [string ]OpenAPIResponse {
434+ 				"200" : {
435+ 					Description : "Successful response" ,
436+ 					Content : map [string ]OpenAPIMedia {
437+ 						"application/json" : {
438+ 							Schema : & OpenAPISchema {
439+ 								Type : "object" ,
440+ 							},
441+ 						},
442+ 					},
443+ 				},
444+ 				"400" : {
445+ 					Description : "Bad request" ,
446+ 					Content : map [string ]OpenAPIMedia {
447+ 						"application/json" : {
448+ 							Schema : & OpenAPISchema {
449+ 								Type : "object" ,
450+ 								Properties : map [string ]OpenAPISchema {
451+ 									"error" : {
452+ 										Type : "object" ,
453+ 										Properties : map [string ]OpenAPISchema {
454+ 											"code" :      {Type : "string" },
455+ 											"message" :   {Type : "string" },
456+ 											"timestamp" : {Type : "string" },
457+ 										},
458+ 									},
459+ 								},
460+ 							},
461+ 						},
462+ 					},
463+ 				},
464+ 			},
465+ 		}
466+ 
467+ 		// Add request body for POST and PUT methods 
468+ 		if  endpoint .Method  ==  "POST"  ||  endpoint .Method  ==  "PUT"  {
469+ 			operation .RequestBody  =  & OpenAPIRequestBody {
470+ 				Required : true ,
471+ 				Content : map [string ]OpenAPIMedia {
472+ 					"application/json" : {
473+ 						Schema : & OpenAPISchema {
474+ 							Type : "object" ,
475+ 						},
476+ 					},
477+ 				},
478+ 			}
479+ 		}
480+ 
481+ 		// Map operation to the appropriate method 
482+ 		switch  endpoint .Method  {
483+ 		case  "GET" :
484+ 			path .Get  =  operation 
485+ 		case  "POST" :
486+ 			path .Post  =  operation 
487+ 		case  "PUT" :
488+ 			path .Put  =  operation 
489+ 		case  "DELETE" :
490+ 			path .Delete  =  operation 
491+ 		}
492+ 
493+ 		spec .Paths [endpoint .Path ] =  path 
494+ 	}
495+ 
496+ 	return  spec 
497+ }
498+ 
499+ // handleOpenAPISpec serves the OpenAPI 3.0 specification at /openapi.json 
500+ func  (s  * ClassificationAPIServer ) handleOpenAPISpec (w  http.ResponseWriter , r  * http.Request ) {
501+ 	spec  :=  s .generateOpenAPISpec ()
502+ 	s .writeJSONResponse (w , http .StatusOK , spec )
503+ }
504+ 
505+ // handleSwaggerUI serves the Swagger UI at /docs 
506+ func  (s  * ClassificationAPIServer ) handleSwaggerUI (w  http.ResponseWriter , r  * http.Request ) {
507+ 	// Serve a simple HTML page that loads Swagger UI from CDN 
508+ 	html  :=  `<!DOCTYPE html> 
509+ <html lang="en"> 
510+ <head> 
511+     <meta charset="UTF-8"> 
512+     <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
513+     <title>Semantic Router API Documentation</title> 
514+     <link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected] /swagger-ui.css"> 515+     <style> 
516+         body { 
517+             margin: 0; 
518+             padding: 0; 
519+         } 
520+     </style> 
521+ </head> 
522+ <body> 
523+     <div id="swagger-ui"></div> 
524+     <script src="https://unpkg.com/[email protected] /swagger-ui-bundle.js"></script> 525+     <script src="https://unpkg.com/[email protected] /swagger-ui-standalone-preset.js"></script> 526+     <script> 
527+         window.onload = function() { 
528+             window.ui = SwaggerUIBundle({ 
529+                 url: "/openapi.json", 
530+                 dom_id: '#swagger-ui', 
531+                 deepLinking: true, 
532+                 presets: [ 
533+                     SwaggerUIBundle.presets.apis, 
534+                     SwaggerUIStandalonePreset 
535+                 ], 
536+                 plugins: [ 
537+                     SwaggerUIBundle.plugins.DownloadUrl 
538+                 ], 
539+                 layout: "StandaloneLayout" 
540+             }); 
541+         }; 
542+     </script> 
543+ </body> 
544+ </html>` 
545+ 
546+ 	w .Header ().Set ("Content-Type" , "text/html; charset=utf-8" )
547+ 	w .WriteHeader (http .StatusOK )
548+ 	w .Write ([]byte (html ))
549+ }
550+ 
319551// handleIntentClassification handles intent classification requests 
320552func  (s  * ClassificationAPIServer ) handleIntentClassification (w  http.ResponseWriter , r  * http.Request ) {
321553	var  req  services.IntentRequest 
0 commit comments