API Reference for .NET Aspire
The Scalar.Aspire package seamlessly integrates API Reference into your .NET Aspire applications, providing a unified documentation interface for all your services.
Overview
Scalar for Aspire provides:
- Unified API Documentation: View documentation for all services in a single, cohesive interface
- Simplified Service Discovery: Automatically discover and configure API endpoints from your Aspire services
- Multiple Document Support: Each service can expose multiple OpenAPI documents
- CORS Issue Elimination: Built-in proxy (enabled by default) handles API requests without requiring CORS configuration
- HTTPS Support: Complete support for both HTTP and HTTPS endpoints with automatic handling
Prerequisites
The Scalar Aspire integration requires a container solution such as Docker or Podman to be installed on your machine.
Service Requirements
Each service you want to include in the API Reference must implement the IResourceWithServiceDiscovery interface and either:
- Expose OpenAPI documents over HTTP or HTTPS endpoints, or
- Provide a static OpenAPI document (a local file) via the file-based
WithApiReferenceoverload
Quick Start
1. Install the Package
dotnet add package Scalar.Aspire
2. Basic Configuration
Add the integration to your AppHost:
using Scalar.Aspire;
var builder = DistributedApplication.CreateBuilder(args);
// Add your services
var userService = builder.AddNpmApp("user-service", "../MyUserService");
var bookService = builder.AddProject<Projects.BookService>("book-service");
// Add API Reference
var scalar = builder.AddScalarApiReference();
// Register services with the API Reference
scalar
.WithApiReference(userService)
.WithApiReference(bookService);
builder.Build().Run();
That's it! 🎉 The Aspire dashboard will display a API Reference resource with unified documentation for all your services.
Configuration
Global Configuration
Configure global settings that apply to all services:
var scalar = builder.AddScalarApiReference(options =>
{
options.WithTheme(ScalarTheme.Purple);
});
Service Configuration
Register services with custom configuration options:
scalar.WithApiReference(bookService, options =>
{
options
.AddDocument("v1", "Book Management API")
.WithOpenApiRoutePattern("/api-documentation/{documentName}.json")
.WithTheme(ScalarTheme.Mars);
});
Multiple OpenAPI Documents
Services can expose multiple OpenAPI documents:
scalar.WithApiReference(catalogService, options =>
{
// Add individual documents with custom titles
options
.AddDocument("v1", "Catalog API v1")
.AddDocument("v2", "Catalog API v2")
.AddDocument("admin", "Admin API", routePattern: "/admin/{documentName}.json");
});
// Or add multiple documents at once
scalar.WithApiReference(userService, options =>
{
options.AddDocuments("public", "internal", "admin");
});
// Set a specific document as default
scalar.WithApiReference(bookService, options =>
{
options
.AddDocument("v1", "Book API v1")
.AddDocument("v2", "Book API v2", isDefault: true)
.AddDocument("beta", "Book API Beta", "/beta/{documentName}.json");
});
Static OpenAPI Documents
Use the file-based overload when a service does not expose a live OpenAPI endpoint—for example, when the description document is generated at build time or when documenting an external API from a local file. The file is mounted into the Scalar container and served at /openapi/{folderPath}/{filename}. When the optional folderPath parameter is not provided, the resource name is used as the folder, so the path is /openapi/{resourceName}/{filename}. You can pass an explicit folderPath to override the default folder or to avoid name collisions when multiple services serve static files. The document URL in the API Reference uses that path; the resourceBuilder is still used to configure the Try It server URL (the API base URL for requests).
Supported file formats: .json, .yaml, .yml.
Default folder (resource name):
scalar.WithApiReference(
myService,
new FileInfo("./openapi/openapi.yaml"),
options => options.AddDocument("v1", "My API"));
Optional custom folder path:
scalar.WithApiReference(
myService,
new FileInfo("./openapi/openapi.yaml"),
folderPath: "my-service",
options => options.AddDocument("v1", "My API"));
Async configuration is also supported:
scalar.WithApiReference(
myService,
new FileInfo("./openapi/openapi.yaml"),
async (options, cancellationToken) =>
{
options.AddDocument("v1", "My API");
// Optional: load secrets, customize theme, etc.
});
Scoping Service Discovery Endpoints
By default, WithApiReference exposes all endpoints of a service to Aspire service discovery. This injects one services__{resourceName}__{scheme}__{index} environment variable per endpoint into the Scalar container (e.g., services__book-service__http__0 and services__book-service__https__0).
Use the optional endpointName parameter to restrict discovery to a single named endpoint:
// Only expose the "https" endpoint for service discovery
scalar.WithApiReference(bookService, endpointName: "https");
When endpointName is provided, only that endpoint is registered, so the Scalar container receives a single services__book-service__https__0 variable and the HTTP endpoint is not injected. This is useful when a service has both HTTP and HTTPS endpoints and you want Scalar to communicate exclusively over HTTPS.
The same parameter is available on the file-based overload:
scalar.WithApiReference(bookService, new FileInfo("openapi.yaml"), endpointName: "https");
Note:
endpointNameaffects only theservices__*environment variables injected for Aspire service discovery. It does not change the source URL of the OpenAPI document.
Advanced: Base document URL
WithBaseDocumentUrl(ReferenceExpression?) controls the base URL used to resolve the OpenAPI document URL. For static files, the integration sets this internally so the document is loaded from the route pattern /openapi/{resourceName}/{filename} (or /openapi/{folderPath}/{filename} when folderPath is specified). You can override it for custom setups—for example, use ReferenceExpression.Empty so the document URL is the route pattern as-is, or pass a different expression to resolve at startup when endpoints are known.
HTTPS Support
Scalar supports HTTPS endpoints.
Basic HTTPS Configuration
var scalar = builder.AddScalarApiReference(options =>
{
options
.PreferHttpsEndpoint() // Use HTTPS endpoints when available
.AllowSelfSignedCertificates(); // Trust self-signed certificates
});
The AllowSelfSignedCertificates() method should only be used in development environments, never in production.
How HTTPS Support Works
- Protocol Selection: HTTP is used by default. Use
PreferHttpsEndpoint()to prioritize HTTPS when available - Automatic Configuration: When HTTPS is preferred, both OpenAPI document routes and server URLs are automatically configured to use HTTPS endpoints
- Automatic Redirects: HTTP to HTTPS redirects are handled automatically with proper header rewriting (localhost only)
- Certificate Validation: Self-signed certificates can be trusted in development using
AllowSelfSignedCertificates() - Fallback Behavior: If HTTPS is preferred but unavailable, HTTP endpoints are used as fallback. Conversely, if no HTTP endpoint is available, HTTPS endpoints are automatically used
Currently, the API Reference interface is hosted over HTTP, even when communicating with HTTPS services. Support for hosting the Scalar interface under HTTPS will be added in a future release.
Proxy Configuration
Scalar for Aspire includes a built-in proxy that is enabled by default to provide seamless integration with your services.
How the Proxy Works
When the proxy is enabled:
- Eliminates CORS Issues: All API requests are routed through the Scalar proxy, avoiding CORS restrictions
- Service Discovery Integration: OpenAPI servers and document routes are configured to use service discovery endpoints through the proxy
- Default Endpoint: The proxy is served at
/scalar-proxy
Disabling the Proxy
You can disable the default proxy if you prefer direct service communication:
var scalar = builder.AddScalarApiReference(options =>
{
options.DisableDefaultProxy();
});
When the proxy is disabled:
- Direct Service Communication: OpenAPI documents and servers point directly to the actual service endpoints
- CORS Configuration Required: You'll need to configure CORS on your services to allow requests from the Scalar interface
Host Header Forwarding
By default, the Scalar proxy rewrites the outgoing Host header to the target authority (for example, an external OpenID Connect provider). This avoids authentication failures with providers that validate the Host header.
If your upstream requires the original incoming host value, you can opt in to forwarding it:
var scalar = builder.AddScalarApiReference(options =>
{
options.ForwardOriginalHostHeader();
});
Use ForwardOriginalHostHeader() only when the upstream explicitly requires the original host.
Authentication
Configure authentication globally or per service to secure your API documentation.
Global Authentication
Apply authentication settings to all services:
var scalar = builder.AddScalarApiReference(options =>
{
options
.AddPreferredSecuritySchemes("OAuth2", "ApiKey")
.AddAuthorizationCodeFlow("OAuth2", flow =>
{
flow
.WithClientId("aspire-client")
.WithAuthorizationUrl("https://auth.example.com/oauth2/authorize")
.WithTokenUrl("https://auth.example.com/oauth2/token");
})
.AddApiKeyAuthentication("ApiKey", apiKey =>
{
apiKey.WithValue("your-development-api-key");
});
});
When the proxy is enabled, OAuth token requests are automatically proxied through the Scalar proxy to avoid CORS issues. However, interactive authorization requests are not proxied since they occur directly in the browser, so authorization URLs must be correctly configured and accessible. See the Aspire playground on GitHub.
Service-Specific Authentication
Configure different authentication for individual services:
scalar
.WithApiReference(weatherService, options =>
{
options
.AddPreferredSecuritySchemes("WeatherApiKey")
.AddApiKeyAuthentication("WeatherApiKey", apiKey =>
{
apiKey.WithValue("weather-service-key");
});
})
.WithApiReference(bookService, options =>
{
options
.AddPreferredSecuritySchemes("BookOAuth")
.AddAuthorizationCodeFlow("BookOAuth", flow =>
{
flow
.WithClientId("book-service-client")
.WithSelectedScopes("books:read", "books:write");
});
});
Asynchronous Configuration
Use async configuration when you need to fetch secrets or perform other asynchronous operations:
scalar.WithApiReference(bookService, async (options, cancellationToken) =>
{
options
.AddDocument("v1", "Book Management API")
.WithOpenApiRoutePattern("/api-documentation/{documentName}.json");
// Fetch API key from secure storage
var apiKey = await secretProvider.GetValueAsync("BOOKS_API_KEY", cancellationToken);
options
.AddPreferredSecuritySchemes("BookApiKey")
.AddApiKeyAuthentication("ApiKey", schema => schema.WithValue(apiKey));
});
Common Pitfalls
YARP Integration Issues
When using YARP (Aspire.Hosting.Yarp), you may encounter proxy routing conflicts. Here are two solutions:
Solution 1: Configure Proxy URL for YARP Routes
Match the proxy URL to your YARP route pattern:
var scalar = builder
.AddScalarApiReference(options =>
{
options.WithProxyUrl("/api-documentation/scalar-proxy");
});
builder
.AddYarp("my-proxy")
.WithConfiguration(yarp =>
{
yarp
.AddRoute("/api-documentation/{**catch-all}", scalar)
.WithTransformPathRemovePrefix("/api-documentation");
});
Solution 2: Disable the Proxy
Alternatively, disable the proxy entirely, but you'll need to handle CORS configuration yourself:
var scalar = builder.AddScalarApiReference(options =>
{
options.DisableDefaultProxy();
});
Additional Resources
For more advanced configuration options see the .NET ASP.NET Core documentation. Many configuration options are similar between Scalar.AspNetCore and Scalar.Aspire integrations, including Agent (AI chat).