Scalar API

Introduction to Scalar API, an extension to OpenAPI for defining API contracts with enhanced tooling

Introduction to Scalar

Scalar is a modern, open-source documentation tool designed specifically for APIs. Scalar provides an enhanced, interactive documentation experience for your API consumers. Unlike traditional documentation tools, Scalar focuses on creating beautiful, user-friendly API reference documentation with interactive request builders, authentication flows, code samples, and more.

Why Use Scalar for API Documentation?

While tools like Swagger/OpenAPI are excellent for API specification and generation, Scalar addresses the next step in the API lifecycle by providing:

  1. Modern, Interactive UI: Clean, responsive design that’s both visually appealing and functional
  2. Request Building and Testing: Built-in request builder that allows users to test API calls directly from documentation
  3. Real-time Code Samples: Automatically generated code snippets in multiple languages including C#, JavaScript, Python, and curl
  4. Dark Mode Support: Enhanced readability with both light and dark modes
  5. Authentication Flows: Integrated authentication experiences
  6. Customizable Themes: Adapt the documentation to match your brand identity
  7. Enhanced Markdown Support: Rich formatting capabilities beyond basic Markdown
  8. Mobile-Friendly Design: Responsive documentation that works well on all devices

Scalar vs. Swagger/OpenAPI

It’s important to understand that Scalar and OpenAPI serve complementary purposes:

FeatureOpenAPI/SwaggerScalar
Primary PurposeAPI specification and contractAPI documentation presentation
FocusMachine-readable definitionHuman-readable documentation
Code GenerationYesNo
Interactive TestingBasicAdvanced
CustomizabilityLimited UI optionsExtensive theming
ImplementationIntegrated into API codeStandalone documentation
ExperienceDeveloper-orientedUser-friendly for all stakeholders

Most modern API projects use both: OpenAPI for specification and Scalar for documentation presentation.

Setting Up Scalar with ASP.NET Core

In this guide, we’ll walk through the process of integrating Scalar with an ASP.NET Core API project. We’ll build on the foundation of OpenAPI/Swagger and enhance it with Scalar.

Prerequisites

Before starting, ensure you have:

  • .NET 8.0 SDK or later installed
  • A code editor like Visual Studio or VS Code
  • Basic familiarity with ASP.NET Core API development
  • Node.js and npm installed (for Scalar CLI)

Step 1: Create an ASP.NET Core API Project

First, let’s create a simple ASP.NET Core API project:

dotnet new webapi -n ScalarDemoApi
cd ScalarDemoApi

This creates a new API project with basic controller and Swagger integration already set up.

Step 2: Configure OpenAPI in ASP.NET Core

Scalar can work directly with your OpenAPI specification, so let’s ensure our API is properly configured:

// Program.cs
using Microsoft.OpenApi.Models;
using System.Reflection;

// Add services to the container.
builder.Services.AddControllers();

// Configure OpenAPI/Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo 
    {
        Title = "Task Management API",
        Version = "v1",
        Description = "A RESTful API for managing tasks and projects",
        Contact = new OpenApiContact
        {
            Name = "API Support Team",
            Email = "[email protected]",
            Url = new Uri("https://example.com/support")
        },
        License = new OpenApiLicense
        {
            Name = "MIT",
            Url = new Uri("https://opensource.org/licenses/MIT")
        }
    });
    
    // Include XML comments if available
    string xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFilename);
    if (File.Exists(xmlPath))
    {
        options.IncludeXmlComments(xmlPath);
    }
    
    // Add security definition
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT"
    });
    
    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

// Build and configure the app
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

Make sure to enable XML documentation in your project file:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
  <NoWarn>$(NoWarn);1591</NoWarn> <!-- Suppress warnings for undocumented public members -->
</PropertyGroup>

Step 3: Create a Sample API Controller

Let’s create a simple task management API:

using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;

namespace ScalarDemoApi.Controllers;

/// <summary>
/// API endpoints for managing tasks
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class TasksController : ControllerBase
{
    private static readonly List<TaskItem> _tasks = new List<TaskItem>
    {
        new TaskItem { Id = 1, Title = "Learn ASP.NET Core", IsCompleted = true },
        new TaskItem { Id = 2, Title = "Implement Scalar documentation", IsCompleted = false },
        new TaskItem { Id = 3, Title = "Deploy to production", IsCompleted = false }
    };

    /// <summary>
    /// Retrieves all tasks
    /// </summary>
    /// <returns>A collection of tasks</returns>
    /// <response code="200">Returns the list of tasks</response>
    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<TaskItem>), StatusCodes.Status200OK)]
    public ActionResult<IEnumerable<TaskItem>> GetTasks()
    {
        return Ok(_tasks);
    }

    /// <summary>
    /// Gets a specific task by its ID
    /// </summary>
    /// <param name="id">The ID of the task to retrieve</param>
    /// <returns>The requested task</returns>
    /// <response code="200">Returns the requested task</response>
    /// <response code="404">If the task doesn't exist</response>
    [HttpGet("{id}")]
    [ProducesResponseType(typeof(TaskItem), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public ActionResult<TaskItem> GetTask(int id)
    {
        TaskItem? task = _tasks.FirstOrDefault(t => t.Id == id);
        
        if (task == null)
        {
            return NotFound();
        }
        
        return Ok(task);
    }

    /// <summary>
    /// Creates a new task
    /// </summary>
    /// <param name="taskCreate">The task details</param>
    /// <returns>The newly created task</returns>
    /// <response code="201">Returns the newly created task</response>
    /// <response code="400">If the request is invalid</response>
    [HttpPost]
    [ProducesResponseType(typeof(TaskItem), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public ActionResult<TaskItem> CreateTask(TaskCreateModel taskCreate)
    {
        int newId = _tasks.Any() ? _tasks.Max(t => t.Id) + 1 : 1;
        
        TaskItem task = new TaskItem
        {
            Id = newId,
            Title = taskCreate.Title,
            Description = taskCreate.Description,
            IsCompleted = false,
            DueDate = taskCreate.DueDate,
            Priority = taskCreate.Priority
        };
        
        _tasks.Add(task);
        
        return CreatedAtAction(nameof(GetTask), new { id = task.Id }, task);
    }

    /// <summary>
    /// Updates an existing task
    /// </summary>
    /// <param name="id">The ID of the task to update</param>
    /// <param name="taskUpdate">The updated task details</param>
    /// <returns>No content</returns>
    /// <response code="204">If the task was successfully updated</response>
    /// <response code="400">If the request is invalid</response>
    /// <response code="404">If the task doesn't exist</response>
    [HttpPut("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public IActionResult UpdateTask(int id, TaskUpdateModel taskUpdate)
    {
        TaskItem? task = _tasks.FirstOrDefault(t => t.Id == id);
        
        if (task == null)
        {
            return NotFound();
        }
        
        // Update task properties
        task.Title = taskUpdate.Title ?? task.Title;
        task.Description = taskUpdate.Description ?? task.Description;
        
        if (taskUpdate.IsCompleted.HasValue)
        {
            task.IsCompleted = taskUpdate.IsCompleted.Value;
        }
        
        if (taskUpdate.DueDate.HasValue)
        {
            task.DueDate = taskUpdate.DueDate.Value;
        }
        
        if (taskUpdate.Priority.HasValue)
        {
            task.Priority = taskUpdate.Priority.Value;
        }
        
        return NoContent();
    }

    /// <summary>
    /// Deletes a specific task
    /// </summary>
    /// <param name="id">The ID of the task to delete</param>
    /// <returns>No content</returns>
    /// <response code="204">If the task was successfully deleted</response>
    /// <response code="404">If the task doesn't exist</response>
    [HttpDelete("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public IActionResult DeleteTask(int id)
    {
        TaskItem? task = _tasks.FirstOrDefault(t => t.Id == id);
        
        if (task == null)
        {
            return NotFound();
        }
        
        _tasks.Remove(task);
        
        return NoContent();
    }
}

/// <summary>
/// Represents a task item
/// </summary>
public class TaskItem
{
    /// <summary>
    /// The unique identifier for the task
    /// </summary>
    /// <example>1</example>
    public int Id { get; set; }
    
    /// <summary>
    /// The title of the task
    /// </summary>
    /// <example>Implement Scalar documentation</example>
    [Required]
    public string Title { get; set; } = string.Empty;
    
    /// <summary>
    /// A detailed description of the task
    /// </summary>
    /// <example>Create a comprehensive guide on using Scalar with ASP.NET Core</example>
    public string? Description { get; set; }
    
    /// <summary>
    /// Whether the task has been completed
    /// </summary>
    /// <example>false</example>
    public bool IsCompleted { get; set; }
    
    /// <summary>
    /// The due date for the task
    /// </summary>
    /// <example>2025-06-01T00:00:00Z</example>
    public DateTime? DueDate { get; set; }
    
    /// <summary>
    /// The priority level of the task
    /// </summary>
    /// <example>Medium</example>
    public TaskPriority Priority { get; set; } = TaskPriority.Medium;
}

/// <summary>
/// Model for creating a new task
/// </summary>
public class TaskCreateModel
{
    /// <summary>
    /// The title of the task
    /// </summary>
    /// <example>Implement Scalar documentation</example>
    [Required]
    [StringLength(100, MinimumLength = 3)]
    public string Title { get; set; } = string.Empty;
    
    /// <summary>
    /// A detailed description of the task
    /// </summary>
    /// <example>Create a comprehensive guide on using Scalar with ASP.NET Core</example>
    public string? Description { get; set; }
    
    /// <summary>
    /// The due date for the task
    /// </summary>
    /// <example>2025-06-01T00:00:00Z</example>
    public DateTime? DueDate { get; set; }
    
    /// <summary>
    /// The priority level of the task
    /// </summary>
    /// <example>Medium</example>
    public TaskPriority Priority { get; set; } = TaskPriority.Medium;
}

/// <summary>
/// Model for updating an existing task
/// </summary>
public class TaskUpdateModel
{
    /// <summary>
    /// The updated title of the task
    /// </summary>
    /// <example>Implement and test Scalar documentation</example>
    [StringLength(100, MinimumLength = 3)]
    public string? Title { get; set; }
    
    /// <summary>
    /// The updated description of the task
    /// </summary>
    /// <example>Create and test a comprehensive guide on using Scalar with ASP.NET Core</example>
    public string? Description { get; set; }
    
    /// <summary>
    /// Whether the task has been completed
    /// </summary>
    /// <example>true</example>
    public bool? IsCompleted { get; set; }
    
    /// <summary>
    /// The updated due date for the task
    /// </summary>
    /// <example>2025-06-15T00:00:00Z</example>
    public DateTime? DueDate { get; set; }
    
    /// <summary>
    /// The updated priority level of the task
    /// </summary>
    /// <example>High</example>
    public TaskPriority? Priority { get; set; }
}

/// <summary>
/// Represents the priority level of a task
/// </summary>
public enum TaskPriority
{
    /// <summary>
    /// Low priority task
    /// </summary>
    Low = 0,
    
    /// <summary>
    /// Medium priority task
    /// </summary>
    Medium = 1,
    
    /// <summary>
    /// High priority task
    /// </summary>
    High = 2,
    
    /// <summary>
    /// Critical priority task
    /// </summary>
    Critical = 3
}

Step 4: Generate OpenAPI Specification

Now that our API is set up, let’s export its OpenAPI specification:

  1. Run your API project:

    dotnet run
    
  2. Navigate to the Swagger JSON endpoint (typically /swagger/v1/swagger.json)

  3. Save this JSON file as openapi.json in a convenient location for our next step.

Step 5: Install Scalar CLI

Scalar provides a command-line interface for working with OpenAPI specifications:

npm install -g @scalar/cli

Step 6: Generate Scalar Documentation

Now we can use the Scalar CLI to create our documentation site:

scalar init --from-openapi openapi.json

This command will create a new Scalar project using your OpenAPI specification. It will prompt you for some information:

  • Project name: Enter the name for your project (e.g., “Task Management API”)
  • Description: Enter a brief description of your API
  • Default author: Enter your name or organization
  • Language: Select your preferred language for code samples (e.g., C#)

The command generates a Scalar project with your API specification.

Step 7: Customize Your Scalar Documentation

You can customize the Scalar documentation by editing the created configuration files:

  1. Open the scalar.json file, which contains the main configuration:
{
  "title": "Task Management API",
  "description": "A RESTful API for managing tasks and projects",
  "logo": {
    "url": "https://example.com/logo.png",
    "altText": "Company Logo"
  },
  "favicon": "https://example.com/favicon.png",
  "colors": {
    "primary": "#2563eb",
    "background": {
      "light": "#ffffff",
      "dark": "#0f172a"
    },
    "text": {
      "light": "#334155",
      "dark": "#e2e8f0"
    }
  },
  "baseUrl": "/api-docs",
  "navigation": [
    {
      "title": "API Reference",
      "items": [
        {
          "title": "Tasks",
          "slug": "/reference/tasks"
        }
      ]
    },
    {
      "title": "Guides",
      "items": [
        {
          "title": "Getting Started",
          "slug": "/guides/getting-started"
        }
      ]
    }
  ]
}
  1. Add additional markdown content in the content directory. For example, create a getting started guide:
// content/guides/getting-started.md

# Getting Started

This guide will help you get started with the Task Management API.

## Authentication

To use this API, you need to authenticate using a JWT token. Include this token in the Authorization header with the Bearer scheme.

```typescript
// Example: Setting up authentication
const token = 'YOUR_JWT_TOKEN';
fetch('https://api.example.com/tasks', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
})

Working with Tasks

Creating a Task

To create a task, send a POST request to the /api/tasks endpoint:

using System.Net.Http.Json;

HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "YOUR_JWT_TOKEN");

var newTask = new
{
    Title = "Implement Scalar documentation",
    Description = "Create a comprehensive guide on using Scalar with ASP.NET Core",
    Priority = 2, // High
    DueDate = "2025-06-01T00:00:00Z"
};

HttpResponseMessage response = await client.PostAsJsonAsync("https://api.example.com/api/tasks", newTask);
response.EnsureSuccessStatusCode();

### Step 8: Preview Your Scalar Documentation

You can preview your documentation locally:

```powershell
scalar preview

This command starts a local server (typically on port 3000) where you can view your documentation.

Step 9: Build and Deploy Your Documentation

When you’re happy with your documentation, you can build it for deployment:

scalar build

This creates a static website in the dist directory that you can deploy to any static hosting provider, such as GitHub Pages, Netlify, Cloudflare or Azure Static Web Apps.

Deployment to Azure Static Web Apps Example

# Install the Azure Static Web Apps CLI
npm install -g @azure/static-web-apps-cli

# Login to Azure
az login

# Create a static web app
az staticwebapp create --name "task-api-docs" --resource-group "api-documentation" --location "eastus2" --source "." --app-location "dist"

Integrating Scalar with ASP.NET Core Application

For a more integrated experience, you can serve your Scalar documentation directly from your ASP.NET Core application:

  1. Build your Scalar documentation to static files:

    scalar build
    
  2. Copy the generated dist directory to your ASP.NET Core project’s wwwroot/docs directory

  3. Configure your ASP.NET Core application to serve these static files:

    // Program.cs
    app.UseStaticFiles(); // This middleware enables serving static files
    
    // You can also add a redirect from /docs to your documentation home page
    app.MapGet("/docs", context => {
        context.Response.Redirect("/docs/index.html");
        return Task.CompletedTask;
    });
    

Advanced Scalar Features

Custom Themes

You can create custom themes by modifying the colors and styles in your scalar.json file:

{
  "colors": {
    "primary": "#3b82f6",
    "secondary": "#14b8a6",
    "background": {
      "light": "#ffffff",
      "dark": "#0f172a"
    },
    "text": {
      "light": "#334155",
      "dark": "#e2e8f0"
    },
    "success": "#10b981",
    "warning": "#f59e0b",
    "error": "#ef4444",
    "surface": {
      "light": "#f8fafc",
      "dark": "#1e293b"
    }
  },
  "font": {
    "family": "Inter, sans-serif",
    "size": "16px",
    "lineHeight": "1.5"
  }
}

API Authentication Flows

Scalar supports various authentication mechanisms directly in your documentation:

{
  "auth": {
    "methods": [
      {
        "type": "bearer",
        "name": "JWT Bearer Token",
        "description": "Enter your JWT token below"
      },
      {
        "type": "apiKey",
        "name": "API Key",
        "description": "Enter your API key",
        "in": "header",
        "keyName": "X-API-Key"
      }
    ]
  }
}

Custom Code Samples

You can add custom code samples to your API endpoints by creating a Markdown file with the same path as your endpoint:

// content/reference/tasks/post.md

# Create a Task

This endpoint allows you to create a new task in the system.

## Request Example

```csharp
using System.Net.Http.Json;
using System.Text.Json;

// Create an instance of HttpClient
HttpClient client = new HttpClient
{
    BaseAddress = new Uri("https://api.example.com")
};

// Add authentication header
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
    "Bearer", 
    "YOUR_JWT_TOKEN"
);

// Create task object
var newTask = new
{
    Title = "Complete API documentation",
    Description = "Finish the Scalar documentation guide",
    Priority = 2, // High priority
    DueDate = DateTimeOffset.Now.AddDays(7)
};

// Send POST request
HttpResponseMessage response = await client.PostAsJsonAsync("/api/tasks", newTask);

// Handle response
if (response.IsSuccessStatusCode)
{
    string content = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Task created successfully: {content}");
}
else
{
    Console.WriteLine($"Error: {response.StatusCode}");
}

Response Example

{
  "id": 4,
  "title": "Complete API documentation",
  "description": "Finish the Scalar documentation guide",
  "isCompleted": false,
  "dueDate": "2025-05-17T10:30:00Z",
  "priority": 2
}

Best Practices for Scalar Documentation

  1. Organize by Resources: Structure your documentation around your API’s key resources

  2. Add Context with Markdown: Use additional markdown files to provide context, guides, and background information

  3. Include Authentication Details: Clearly explain authentication mechanisms and provide setup instructions

  4. Provide Code Examples: Include code examples in multiple languages for each endpoint

  5. Use Response Examples: Show what successful responses look like for each endpoint

  6. Include Error Handling: Document possible error responses and how to handle them

  7. Keep OpenAPI Spec Updated: Since Scalar builds on your OpenAPI specification, keep it updated as your API evolves

  8. Versioning: Include versioning information and explain your versioning strategy

  9. Use Tags for Organization: Use OpenAPI tags to organize your endpoints logically

  10. Add Request/Response Schemas: Include detailed schemas for all requests and responses

Common Issues and Troubleshooting

Missing XML Documentation

If your API descriptions aren’t showing up in Scalar:

  1. Ensure XML documentation is enabled in your .csproj file:

    <PropertyGroup>
      <GenerateDocumentationFile>true</GenerateDocumentationFile>
    </PropertyGroup>
    
  2. Make sure you’ve added XML comments to your controllers and models.

Authentication Not Working in Interactive Examples

If the authentication in your interactive examples isn’t working:

  1. Check that you’ve correctly configured the auth section in your scalar.json
  2. Ensure your API allows CORS for the Scalar documentation domain

Custom Examples Not Displaying

If your custom examples aren’t showing:

  1. Verify the path structure matches your API endpoint structure
  2. Check that your markdown files are in the correct location

Resources and Further Learning

Conclusion

By combining ASP.NET Core’s powerful API capabilities, the OpenAPI specification, and Scalar’s modern documentation interface, you can create API documentation that is both comprehensive and user-friendly. This approach not only helps consumers understand your API more quickly but also encourages proper API usage and reduces support overhead.

Scalar represents a significant step forward in API documentation, focusing on the user experience while still maintaining the technical accuracy and completeness that developers need.