Refitter 0.9.9

dotnet tool install --global Refitter --version 0.9.9
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest # if you are setting up this repo
dotnet tool install --local Refitter --version 0.9.9
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Refitter&version=0.9.9
nuke :add-package Refitter --version 0.9.9

Refitter

Refitter is a CLI tool for generating a C# REST API Client using the Refit library. Refitter can generate the Refit interface from OpenAPI specifications

Installation:

The tool is packaged as a .NET Tool and is published to nuget.org. You can install the latest version of this tool like this:

dotnet tool install --global Refitter

Usage:

$ refitter --help
USAGE:
    refitter [URL or input file] [OPTIONS]

EXAMPLES:
    refitter ./openapi.json
    refitter https://petstore3.swagger.io/api/v3/openapi.yaml
    refitter ./openapi.json --settings-file ./openapi.refitter --output ./GeneratedCode.cs
    refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --output ./GeneratedCode.cs
    refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --internal
    refitter ./openapi.json --output ./IGeneratedCode.cs --interface-only
    refitter ./openapi.json --use-api-response
    refitter ./openapi.json --cancellation-tokens
    refitter ./openapi.json --no-operation-headers
    refitter ./openapi.json --no-accept-headers
    refitter ./openapi.json --use-iso-date-format
    refitter ./openapi.json --additional-namespace "Your.Additional.Namespace" --additional-namespace "Your.Other.Additional.Namespace"
    refitter ./openapi.json --multiple-interfaces ByEndpoint
    refitter ./openapi.json --tag Pet --tag Store --tag User
    refitter ./openapi.json --match-path '^/pet/.*'
    refitter ./openapi.json --trim-unused-schema
    refitter ./openapi.json --trim-unused-schema  --keep-schema '^Model$' --keep-schema '^Person.+'
    refitter ./openapi.json --no-deprecated-operations
    refitter ./openapi.json --operation-name-template '{operationName}Async'
    refitter ./openapi.json --optional-nullable-parameters

ARGUMENTS:
    [URL or input file]    URL or file path to OpenAPI Specification file

OPTIONS:
                                                DEFAULT                                                                                                                                                    
    -h, --help                                                   Prints help information                                                                                                                   
    -v, --version                                                Prints version information                                                                                                                
    -s, --settings-file                                          Path to .refitter settings file. Specifying this will ignore all other settings (except for --output)                                     
    -n, --namespace                             GeneratedCode    Default namespace to use for generated types                                                                                              
    -o, --output                                Output.cs        Path to Output file                                                                                                                       
        --no-auto-generated-header                               Don't add <auto-generated> header to output file                                                                                          
        --no-accept-headers                                      Don't add <Accept> header to output file                                                                                                  
        --interface-only                                         Don't generate contract types                                                                                                             
        --use-api-response                                       Return Task<IApiResponse<T>> instead of Task<T>                                                                                           
        --use-observable-response                                Return IObservable instead of Task                                                                                                        
        --internal                                               Set the accessibility of the generated types to 'internal'                                                                                
        --cancellation-tokens                                    Use cancellation tokens                                                                                                                   
        --no-operation-headers                                   Don't generate operation headers                                                                                                          
        --no-logging                                             Don't log errors or collect telemetry                                                                                                     
        --additional-namespace                                   Add additional namespace to generated types                                                                                               
        --use-iso-date-format                                    Explicitly format date query string parameters in ISO 8601 standard date format using delimiters (2023-06-15)                             
        --multiple-interfaces                                    Generate a Refit interface for each endpoint. May be one of ByEndpoint, ByTag                                                             
        --match-path                                             Only include Paths that match the provided regular expression. May be set multiple times                                                  
        --tag                                                    Only include Endpoints that contain this tag. May be set multiple times and result in OR'ed evaluation                                    
        --skip-validation                                        Skip validation of the OpenAPI specification                                                                                              
        --no-deprecated-operations                               Don't generate deprecated operations                                                                                                      
        --operation-name-template                                Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface
        --optional-nullable-parameters                           Generate nullable parameters as optional parameters                                                                                       
        --trim-unused-schema                                     Removes unreferenced components schema to keep the generated output to a minimum                                                          
        --keep-schema                                            Force to keep matching schema, uses regular expressions. Use together with "--trim-unused-schema". Can be set multiple times              
        --no-banner                                              Don't show donation banner                                                                                                                
        --skip-default-additional-properties                     Set to true to skip default additional properties                                                                                         
        --operation-name-generator              Default          The NSwag IOperationNameGenerator implementation to use.                                                                                  
                                                                 May be one of:                                                                                                                            
                                                                 - Default                                                                                                                                 
                                                                 - MultipleClientsFromOperationId                                                                                                          
                                                                 - MultipleClientsFromPathSegments                                                                                                         
                                                                 - MultipleClientsFromFirstTagAndOperationId                                                                                               
                                                                 - MultipleClientsFromFirstTagAndOperationName                                                                                             
                                                                 - MultipleClientsFromFirstTagAndPathSegments                                                                                              
                                                                 - SingleClientFromOperationId                                                                                                             
                                                                 - SingleClientFromPathSegments                                                                                                            
                                                                 See https://refitter.github.io/api/Refitter.Core.OperationNameGeneratorTypes.html for more information                                    

.Refitter File format

The following is an example .refitter file

{
  "openApiPath": "/path/to/your/openAPI", // Required
  "namespace": "Org.System.Service.Api.GeneratedCode", // Optional. Default=GeneratedCode
  "naming": {
    "useOpenApiTitle": false, // Optional. Default=true
    "interfaceName": "MyApiClient" // Optional. Default=ApiClient
  },
  "generateContracts": true, // Optional. Default=true
  "generateXmlDocCodeComments": true, // Optional. Default=true
  "generateStatusCodeComments": true, // Optional. Default=true
  "addAutoGeneratedHeader": true, // Optional. Default=true
  "addAcceptHeaders": true, // Optional. Default=true
  "returnIApiResponse": false, // Optional. Default=false
  "responseTypeOverride": { // Optional. Default={}
    "File_Upload": "IApiResponse",
    "File_Download": "System.Net.Http.HttpContent"
  },
  "generateOperationHeaders": true, // Optional. Default=true
  "typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
  "useCancellationTokens": false, // Optional. Default=false
  "useIsoDateFormat": false, // Optional. Default=false
  "multipleInterfaces": "ByEndpoint", // Optional. May be one of "ByEndpoint" or "ByTag"
  "generateDeprecatedOperations": false, // Optional. Default=true
  "operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName} when multipleInterfaces != ByEndpoint
  "optionalParameters": false, // Optional. Default=false
  "outputFolder": "../CustomOutput" // Optional. Default=./Generated
  "outputFilename": "RefitInterface.cs", // Optional. Default=Output.cs for CLI tool
  "additionalNamespaces": [ // Optional
    "Namespace1",
    "Namespace2"
  ],
  "includeTags": [ // Optional. OpenAPI Tag to include when generating code
    "Pet",
    "Store",
    "User"
  ],
  "includePathMatches": [ // Optional. Only include Paths that match the provided regular expression
    "^/pet/.*",
    "^/store/.*"
  ],
  "trimUnusedSchema": false, // Optional. Default=false
  "keepSchemaPatterns": [ // Optional. Force to keep matching schema, uses regular expressions. Use together with trimUnusedSchema=true
    "^Model$",
    "^Person.+"
  ],
  "generateDefaultAdditionalProperties": true, // Optional. default=true
  "operationNameGenerator": "Default", // Optional. May be one of Default, MultipleClientsFromOperationId, MultipleClientsFromPathSegments, MultipleClientsFromFirstTagAndOperationId, MultipleClientsFromFirstTagAndOperationName, MultipleClientsFromFirstTagAndPathSegments, SingleClientFromOperationId, SingleClientFromPathSegments
  "dependencyInjectionSettings": { // Optional
    "baseUrl": "https://petstore3.swagger.io/api/v3", // Optional. Leave this blank to set the base address manually
    "httpMessageHandlers": [ // Optional
        "AuthorizationMessageHandler", 
        "TelemetryMessageHandler" 
    ],
    "usePolly": true, // Optional. Set this to true, to configure Polly with a retry policy that uses a jittered backoff. Default=false
    "pollyMaxRetryCount": 3, // Optional. Default=6
    "firstBackoffRetryInSeconds": 0.5 // Optional. Default=1.0
  },
  "codeGeneratorSettings": { // Optional. Default settings are the values set in this example
    "requiredPropertiesMustBeDefined": true,
    "generateDataAnnotations": true,
    "anyType": "object",
    "dateType": "System.DateTimeOffset",
    "dateTimeType": "System.DateTimeOffset",
    "timeType": "System.TimeSpan",
    "timeSpanType": "System.TimeSpan",
    "arrayType": "System.Collections.Generic.ICollection",
    "dictionaryType": "System.Collections.Generic.IDictionary",
    "arrayInstanceType": "System.Collections.ObjectModel.Collection",
    "dictionaryInstanceType": "System.Collections.Generic.Dictionary",
    "arrayBaseType": "System.Collections.ObjectModel.Collection",
    "dictionaryBaseType": "System.Collections.Generic.Dictionary",
    "propertySetterAccessModifier": "",
    "generateImmutableArrayProperties": false,
    "generateImmutableDictionaryProperties": false,
    "handleReferences": false,
    "jsonSerializerSettingsTransformationMethod": null,
    "generateJsonMethods": false,
    "enforceFlagEnums": false,
    "inlineNamedDictionaries": false,
    "inlineNamedTuples": true,
    "inlineNamedArrays": false,
    "generateOptionalPropertiesAsNullable": false,
    "generateNullableReferenceTypes": false,
    "generateNativeRecords": false,
    "generateDefaultValues": true,
    "inlineNamedAny": false,
    "excludedTypeNames": [
      "ExcludedTypeFoo",
      "ExcludedTypeBar"
    ]
  }
}
  • openApiPath - points to the OpenAPI Specifications file. This can be the path to a file stored on disk, relative to the .refitter file. This can also be a URL to a remote file that will be downloaded over HTTP/HTTPS
  • namespace - the namespace used in the generated code. If not specified, this defaults to GeneratedCode
  • naming.useOpenApiTitle - a boolean indicating whether the OpenApi title should be used. Default is true
  • naming.interfaceName - the name of the generated interface. The generated code will automatically prefix this with I so if this set to MyApiClient then the generated interface is called IMyApiClient. Default is ApiClient
  • generateContracts - a boolean indicating whether contracts should be generated. A use case for this is several API clients use the same contracts. Default is true
  • generateXmlDocCodeComments - a boolean indicating whether XML doc comments should be generated. Default is true
  • addAutoGeneratedHeader - a boolean indicating whether XML doc comments should be generated. Default is true
  • addAcceptHeaders - a boolean indicating whether to add accept headers [Headers("Accept: application/json")]. Default is true
  • returnIApiResponse - a boolean indicating whether to return IApiResponse<T> objects. Default is false
  • responseTypeOverride - a dictionary with operation ids (as specified in the OpenAPI document) and a particular return type to use. The types are wrapped in a task, but otherwise unmodified (so make sure to specify or import their namespaces). Default is {}
  • generateOperationHeaders - a boolean indicating whether to use operation headers in the generated methods. Default is true
  • typeAccessibility - the generated type accessibility. Possible values are Public and Internal. Default is Public
  • useCancellationTokens - Use cancellation tokens in the generated methods. Default is false
  • useIsoDateFormat - Set to true to explicitly format date query string parameters in ISO 8601 standard date format using delimiters (for example: 2023-06-15). Default is false
  • multipleInterfaces - Set to ByEndpoint to generate an interface for each endpoint, or ByTag to group Endpoints by their Tag (like SwaggerUI groups them).
  • outputFolder - a string describing a relative path to a desired output folder. Default is ./Generated
  • outputFilename - Output filename. Default is Output.cs when used from the CLI tool, otherwise its the .refitter filename. So Petstore.refitter becomes Petstore.cs.
  • additionalNamespaces - A collection of additional namespaces to include in the generated file. A use case for this is when you want to reuse contracts from a different namespace than the generated code. Default is empty
  • includeTags - A collection of tags to use a filter for including endpoints that contain this tag.
  • includePathMatches - A collection of regular expressions used to filter paths.
  • generateDeprecatedOperations - a boolean indicating whether deprecated operations should be generated or skipped. Default is true
  • operationNameTemplate - Generate operation names using pattern. This must contain the string {operationName}. An example usage of this could be {operationName}Async to suffix all method names with Async
  • optionalParameters - Generate non-required parameters as nullable optional parameters
  • trimUnusedSchema - Removes unreferenced components schema to keep the generated output to a minimum
  • keepSchemaPatterns: A collection of regular expressions to force to keep matching schema. This is used together with trimUnusedSchema
  • generateDefaultAdditionalProperties: Set to false to skip default additional properties. Default is true
  • operationNameGenerator: The NSwag IOperationNameGenerator implementation to use. See https://refitter.github.io/api/Refitter.Core.OperationNameGeneratorTypes.html
  • dependencyInjectionSettings - Setting this will generated extension methods to IServiceCollection for configuring Refit clients
    • baseUrl - Used as the HttpClient base address. Leave this blank to manually set the base URL
    • httpMessageHandlers - A collection of HttpMessageHandler that is added to the HttpClient pipeline
    • usePolly - Set this to true to configure the HttpClient to use Polly using a retry policy with a jittered backoff
    • pollyMaxRetryCount - This is the max retry count used in the Polly retry policy. Default is 6
    • firstBackoffRetryInSeconds - This is the duration of the initial retry backoff. Default is 1 second
  • codeGeneratorSettings - Setting this allows customization of the NSwag generated types and contracts
    • requiredPropertiesMustBeDefined - Default is true,
    • generateDataAnnotations - Default is true,
    • anyType - Default is object,
    • dateType - Default is System.DateTimeOffset,
    • dateTimeType - Default is System.DateTimeOffset,
    • timeType - Default is System.TimeSpan,
    • timeSpanType - Default is System.TimeSpan,
    • arrayType - Default is System.Collections.Generic.ICollection,
    • dictionaryType - Default is System.Collections.Generic.IDictionary,
    • arrayInstanceType - Default is System.Collections.ObjectModel.Collection,
    • dictionaryInstanceType - Default is System.Collections.Generic.Dictionary,
    • arrayBaseType - Default is System.Collections.ObjectModel.Collection,
    • dictionaryBaseType - Default is System.Collections.Generic.Dictionary,
    • propertySetterAccessModifier - Default is ``,
    • generateImmutableArrayProperties - Default is false,
    • generateImmutableDictionaryProperties - Default is false,
    • handleReferences - Default is false,
    • jsonSerializerSettingsTransformationMethod - Default is null,
    • generateJsonMethods - Default is false,
    • enforceFlagEnums - Default is false,
    • inlineNamedDictionaries - Default is false,
    • inlineNamedTuples - Default is true,
    • inlineNamedArrays - Default is false,
    • generateOptionalPropertiesAsNullable - Default is false,
    • generateNullableReferenceTypes - Default is false,
    • generateNativeRecords - Default is false
    • generateDefaultValues - Default is true
    • inlineNamedAny - Default is false
    • excludedTypeNames - Default is empty

To generate code from an OpenAPI specifications file, run the following:

$ refitter [path to OpenAPI spec file] --namespace "[Your.Namespace.Of.Choice.GeneratedCode]"

This will generate a file called Output.cs which contains the Refit interface and contract classes generated using NSwag

Using the generated code

Here's an example generated output from the Swagger Petstore example using the default settings

$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode"
using Refit;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Your.Namespace.Of.Choice.GeneratedCode
{
    [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
    public partial interface ISwaggerPetstore
    {
        /// <summary>Update an existing pet</summary>
        /// <remarks>Update an existing pet by Id</remarks>
        /// <param name="body">Update an existent pet in the store</param>
        /// <returns>Successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Pet not found</description>
        /// </item>
        /// <item>
        /// <term>405</term>
        /// <description>Validation exception</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/xml, application/json")]
        [Put("/pet")]
        Task<Pet> UpdatePet([Body] Pet body);

        /// <summary>Add a new pet to the store</summary>
        /// <remarks>Add a new pet to the store</remarks>
        /// <param name="body">Create a new pet in the store</param>
        /// <returns>Successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/xml, application/json")]
        [Post("/pet")]
        Task<Pet> AddPet([Body] Pet body);

        /// <summary>Finds Pets by status</summary>
        /// <remarks>Multiple status values can be provided with comma separated strings</remarks>
        /// <param name="status">Status values that need to be considered for filter</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid status value</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/pet/findByStatus")]
        Task<ICollection<Pet>> FindPetsByStatus([Query] Status? status);

        /// <summary>Finds Pets by tags</summary>
        /// <remarks>Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.</remarks>
        /// <param name="tags">Tags to filter by</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid tag value</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/pet/findByTags")]
        Task<ICollection<Pet>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable<string> tags);

        /// <summary>Find pet by ID</summary>
        /// <remarks>Returns a single pet</remarks>
        /// <param name="petId">ID of pet to return</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Pet not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/xml, application/json")]
        [Get("/pet/{petId}")]
        Task<Pet> GetPetById(long petId);

        /// <summary>Updates a pet in the store with form data</summary>
        /// <param name="petId">ID of pet that needs to be updated</param>
        /// <param name="name">Name of pet that needs to be updated</param>
        /// <param name="status">Status of pet that needs to be updated</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </exception>
        [Post("/pet/{petId}")]
        Task UpdatePetWithForm(long petId, [Query] string name, [Query] string status);

        /// <summary>Deletes a pet</summary>
        /// <param name="petId">Pet id to delete</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid pet value</description>
        /// </item>
        /// </list>
        /// </exception>
        [Delete("/pet/{petId}")]
        Task DeletePet(long petId, [Header("api_key")] string api_key);

        /// <summary>uploads an image</summary>
        /// <param name="petId">ID of pet to update</param>
        /// <param name="additionalMetadata">Additional Metadata</param>
        /// <returns>
        /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>200</term>
        /// <description>successful operation</description>
        /// </item>
        /// </list>
        /// </returns>
        [Headers("Accept: application/json")]
        [Post("/pet/{petId}/uploadImage")]
        Task<ApiResponse> UploadFile(long petId, [Query] string additionalMetadata,  StreamPart body);

        /// <summary>Returns pet inventories by status</summary>
        /// <remarks>Returns a map of status codes to quantities</remarks>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Headers("Accept: application/json")]
        [Get("/store/inventory")]
        Task<IDictionary<string, int>> GetInventory();

        /// <summary>Place an order for a pet</summary>
        /// <remarks>Place a new order in the store</remarks>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>405</term>
        /// <description>Invalid input</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Post("/store/order")]
        Task<Order> PlaceOrder([Body] Order body);

        /// <summary>Find purchase order by ID</summary>
        /// <remarks>For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions</remarks>
        /// <param name="orderId">ID of order that needs to be fetched</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Order not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/store/order/{orderId}")]
        Task<Order> GetOrderById(long orderId);

        /// <summary>Delete purchase order by ID</summary>
        /// <remarks>For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors</remarks>
        /// <param name="orderId">ID of the order that needs to be deleted</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid ID supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>Order not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Delete("/store/order/{orderId}")]
        Task DeleteOrder(long orderId);

        /// <summary>Create user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="body">Created user object</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Headers("Accept: application/json, application/xml")]
        [Post("/user")]
        Task CreateUser([Body] User body);

        /// <summary>Creates list of users with given input array</summary>
        /// <remarks>Creates list of users with given input array</remarks>
        /// <returns>Successful operation</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Headers("Accept: application/xml, application/json")]
        [Post("/user/createWithList")]
        Task<User> CreateUsersWithListInput([Body] IEnumerable<User> body);

        /// <summary>Logs user into the system</summary>
        /// <param name="username">The user name for login</param>
        /// <param name="password">The password for login in clear text</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username/password supplied</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/user/login")]
        Task<string> LoginUser([Query] string username, [Query] string password);

        /// <summary>Logs out current logged in user session</summary>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Get("/user/logout")]
        Task LogoutUser();

        /// <summary>Get user by user name</summary>
        /// <param name="username">The name that needs to be fetched. Use user1 for testing.</param>
        /// <returns>successful operation</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>User not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Accept: application/json")]
        [Get("/user/{username}")]
        Task<User> GetUserByName(string username);

        /// <summary>Update user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="username">name that need to be deleted</param>
        /// <param name="body">Update an existent user in the store</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
        [Put("/user/{username}")]
        Task UpdateUser(string username, [Body] User body);

        /// <summary>Delete user</summary>
        /// <remarks>This can only be done by the logged in user.</remarks>
        /// <param name="username">The name that needs to be deleted</param>
        /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>400</term>
        /// <description>Invalid username supplied</description>
        /// </item>
        /// <item>
        /// <term>404</term>
        /// <description>User not found</description>
        /// </item>
        /// </list>
        /// </exception>
        [Delete("/user/{username}")]
        Task DeleteUser(string username);
    }
}

Here's an example generated output from the Swagger Petstore example configured to generate an interface for each endpoint

CLI Tool

$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --multiple-interfaces ByEndpoint

Output

/// <summary>Update an existing pet</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUpdatePetEndpoint
{
    /// <summary>Update an existing pet</summary>
    /// <remarks>Update an existing pet by Id</remarks>
    /// <param name="body">Update an existent pet in the store</param>
    /// <returns>Successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Pet not found</description>
    /// </item>
    /// <item>
    /// <term>405</term>
    /// <description>Validation exception</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/xml, application/json")]
    [Put("/pet")]
    Task<Pet> Execute([Body] Pet body);
}

/// <summary>Add a new pet to the store</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IAddPetEndpoint
{
    /// <summary>Add a new pet to the store</summary>
    /// <remarks>Add a new pet to the store</remarks>
    /// <param name="body">Create a new pet in the store</param>
    /// <returns>Successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>405</term>
    /// <description>Invalid input</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/xml, application/json")]
    [Post("/pet")]
    Task<Pet> Execute([Body] Pet body);
}

/// <summary>Finds Pets by status</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IFindPetsByStatusEndpoint
{
    /// <summary>Finds Pets by status</summary>
    /// <remarks>Multiple status values can be provided with comma separated strings</remarks>
    /// <param name="status">Status values that need to be considered for filter</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid status value</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/pet/findByStatus")]
    Task<ICollection<Pet>> Execute([Query] Status? status);
}

/// <summary>Finds Pets by tags</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IFindPetsByTagsEndpoint
{
    /// <summary>Finds Pets by tags</summary>
    /// <remarks>Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.</remarks>
    /// <param name="tags">Tags to filter by</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid tag value</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/pet/findByTags")]
    Task<ICollection<Pet>> Execute([Query(CollectionFormat.Multi)] IEnumerable<string> tags);
}

/// <summary>Find pet by ID</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetPetByIdEndpoint
{
    /// <summary>Find pet by ID</summary>
    /// <remarks>Returns a single pet</remarks>
    /// <param name="petId">ID of pet to return</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Pet not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/xml, application/json")]
    [Get("/pet/{petId}")]
    Task<Pet> Execute(long petId);
}

/// <summary>Updates a pet in the store with form data</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUpdatePetWithFormEndpoint
{
    /// <summary>Updates a pet in the store with form data</summary>
    /// <param name="petId">ID of pet that needs to be updated</param>
    /// <param name="name">Name of pet that needs to be updated</param>
    /// <param name="status">Status of pet that needs to be updated</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>405</term>
    /// <description>Invalid input</description>
    /// </item>
    /// </list>
    /// </exception>
    [Post("/pet/{petId}")]
    Task Execute(long petId, [Query] string name, [Query] string status);
}

/// <summary>Deletes a pet</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IDeletePetEndpoint
{
    /// <summary>Deletes a pet</summary>
    /// <param name="petId">Pet id to delete</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid pet value</description>
    /// </item>
    /// </list>
    /// </exception>
    [Delete("/pet/{petId}")]
    Task Execute(long petId, [Header("api_key")] string api_key);
}

/// <summary>uploads an image</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUploadFileEndpoint
{
    /// <summary>uploads an image</summary>
    /// <param name="petId">ID of pet to update</param>
    /// <param name="additionalMetadata">Additional Metadata</param>
    /// <returns>
    /// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>200</term>
    /// <description>successful operation</description>
    /// </item>
    /// </list>
    /// </returns>
    [Headers("Accept: application/json")]
    [Post("/pet/{petId}/uploadImage")]
    Task<ApiResponse> Execute(long petId, [Query] string additionalMetadata,  StreamPart body);
}

/// <summary>Returns pet inventories by status</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetInventoryEndpoint
{
    /// <summary>Returns pet inventories by status</summary>
    /// <remarks>Returns a map of status codes to quantities</remarks>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Headers("Accept: application/json")]
    [Get("/store/inventory")]
    Task<IDictionary<string, int>> Execute();
}

/// <summary>Place an order for a pet</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IPlaceOrderEndpoint
{
    /// <summary>Place an order for a pet</summary>
    /// <remarks>Place a new order in the store</remarks>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>405</term>
    /// <description>Invalid input</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Post("/store/order")]
    Task<Order> Execute([Body] Order body);
}

/// <summary>Find purchase order by ID</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetOrderByIdEndpoint
{
    /// <summary>Find purchase order by ID</summary>
    /// <remarks>For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions</remarks>
    /// <param name="orderId">ID of order that needs to be fetched</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Order not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/store/order/{orderId}")]
    Task<Order> Execute(long orderId);
}

/// <summary>Delete purchase order by ID</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IDeleteOrderEndpoint
{
    /// <summary>Delete purchase order by ID</summary>
    /// <remarks>For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors</remarks>
    /// <param name="orderId">ID of the order that needs to be deleted</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid ID supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>Order not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Delete("/store/order/{orderId}")]
    Task Execute(long orderId);
}

/// <summary>Create user</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ICreateUserEndpoint
{
    /// <summary>Create user</summary>
    /// <remarks>This can only be done by the logged in user.</remarks>
    /// <param name="body">Created user object</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Headers("Accept: application/json, application/xml")]
    [Post("/user")]
    Task Execute([Body] User body);
}

/// <summary>Creates list of users with given input array</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ICreateUsersWithListInputEndpoint
{
    /// <summary>Creates list of users with given input array</summary>
    /// <remarks>Creates list of users with given input array</remarks>
    /// <returns>Successful operation</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Headers("Accept: application/xml, application/json")]
    [Post("/user/createWithList")]
    Task<User> Execute([Body] IEnumerable<User> body);
}

/// <summary>Logs user into the system</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ILoginUserEndpoint
{
    /// <summary>Logs user into the system</summary>
    /// <param name="username">The user name for login</param>
    /// <param name="password">The password for login in clear text</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid username/password supplied</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/user/login")]
    Task<string> Execute([Query] string username, [Query] string password);
}

/// <summary>Logs out current logged in user session</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface ILogoutUserEndpoint
{
    /// <summary>Logs out current logged in user session</summary>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Get("/user/logout")]
    Task Execute();
}

/// <summary>Get user by user name</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IGetUserByNameEndpoint
{
    /// <summary>Get user by user name</summary>
    /// <param name="username">The name that needs to be fetched. Use user1 for testing.</param>
    /// <returns>successful operation</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid username supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>User not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Headers("Accept: application/json")]
    [Get("/user/{username}")]
    Task<User> Execute(string username);
}

/// <summary>Update user</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUpdateUserEndpoint
{
    /// <summary>Update user</summary>
    /// <remarks>This can only be done by the logged in user.</remarks>
    /// <param name="username">name that need to be deleted</param>
    /// <param name="body">Update an existent user in the store</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
    [Put("/user/{username}")]
    Task Execute(string username, [Body] User body);
}

/// <summary>Delete user</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IDeleteUserEndpoint
{
    /// <summary>Delete user</summary>
    /// <remarks>This can only be done by the logged in user.</remarks>
    /// <param name="username">The name that needs to be deleted</param>
    /// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
    /// <exception cref="ApiException">
    /// Thrown when the request returns a non-success status code:
    /// <list type="table">
    /// <listheader>
    /// <term>Status</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term>400</term>
    /// <description>Invalid username supplied</description>
    /// </item>
    /// <item>
    /// <term>404</term>
    /// <description>User not found</description>
    /// </item>
    /// </list>
    /// </exception>
    [Delete("/user/{username}")]
    Task Execute(string username);
}

RestService

Here's an example usage of the generated code above

using Refit;
using System;
using System.Threading.Tasks;

namespace Your.Namespace.Of.Choice.GeneratedCode;

internal class Program
{
    private static async Task Main(string[] args)
    {
        var client = RestService.For<ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
        var pet = await client.GetPetById(1);

        Console.WriteLine("## Using Task<T> as return type ##");
        Console.WriteLine($"Name: {pet.Name}");
        Console.WriteLine($"Category: {pet.Category.Name}");
        Console.WriteLine($"Status: {pet.Status}");
        Console.WriteLine();

        var client2 = RestService.For<WithApiResponse.ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
        var response = await client2.GetPetById(2);

        Console.WriteLine("## Using Task<IApiResponse<T>> as return type ##");
        Console.WriteLine($"HTTP Status Code: {response.StatusCode}");
        Console.WriteLine($"Name: {response.Content.Name}");
        Console.WriteLine($"Category: {response.Content.Category.Name}");
        Console.WriteLine($"Status: {response.Content.Status}");
    }
}

The RestService class generates an implementation of ISwaggerPetstore that uses HttpClient to make its calls.

The code above when run will output something like this:

## Using Task<T> as return type ##
Name: Gatitotototo
Category: Chaucito
Status: Sold

## Using Task<IApiResponse<T>> as return type ##
HTTP Status Code: OK
Name: Gatitotototo
Category: Chaucito
Status: Sold

ASP.NET Core and HttpClientFactory

Here's an example Minimal API with the Refit.HttpClientFactory library:

using Refit;
using Your.Namespace.Of.Choice.GeneratedCode;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services
    .AddRefitClient<ISwaggerPetstore>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://petstore3.swagger.io/api/v3"));

var app = builder.Build();
app.MapGet(
        "/pet/{id:long}",
        async (ISwaggerPetstore petstore, long id) =>
        {
            try
            {
                return Results.Ok(await petstore.GetPetById(id));
            }
            catch (Refit.ApiException e)
            {
                return Results.StatusCode((int)e.StatusCode);
            }
        })
    .WithName("GetPetById")
    .WithOpenApi();

app.UseHttpsRedirection();
app.UseSwaggerUI();
app.UseSwagger();
app.Run();

.NET Core supports registering the generated ISwaggerPetstore interface via HttpClientFactory

The following request to the API above

$ curl -X 'GET' 'https://localhost:5001/pet/1' -H 'accept: application/json'

Returns a response that looks something like this:

{
  "id": 1,
  "name": "Special_char_owner_!@#$^&()`.testing",
  "photoUrls": [
    "https://petstore3.swagger.io/resources/photos/623389095.jpg"
  ],
  "tags": [],
  "status": "Sold"
}

System requirements

Refitter supports .NET versions with current long-term support (6.0 and 8.0)

For tips and tricks on software development, check out my blog

If you find this useful and feel a bit generous then feel free to buy me a coffee ☕

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last updated
0.9.9 7,979 3/7/2024
0.9.8 2,430 2/27/2024
0.9.7 71,914 2/7/2024
0.9.6 201 1/29/2024
0.9.5 35,343 1/15/2024
0.9.4.43-preview 76 1/15/2024
0.9.4 32,028 1/12/2024
0.9.3.42-preview 112 1/10/2024
0.9.2 20,059 1/10/2024
0.9.1 3,845 1/9/2024
0.9.0 3,041 1/9/2024
0.8.7.41-preview 111 1/3/2024
0.8.7.40-preview 88 12/20/2023
0.8.7 54,973 12/18/2023
0.8.6.39-preview 107 12/14/2023
0.8.6.38-preview 121 12/14/2023
0.8.6 3,491 12/11/2023
0.8.5 52,468 11/23/2023
0.8.4 321 11/7/2023
0.8.3 209 10/31/2023
0.8.2 374 10/9/2023
0.8.1 840 10/4/2023
0.8.0 1,710 9/23/2023
0.7.5 2,694 9/7/2023
0.7.4 311 9/6/2023
0.7.3.37-preview 173 8/25/2023
0.7.3.36-preview 192 8/25/2023
0.7.3.35-preview 207 8/21/2023
0.7.3.34-preview 211 8/15/2023
0.7.3.33-preview 208 8/12/2023
0.7.3 1,961 8/26/2023
0.7.2.32-preview 168 8/7/2023
0.7.2 3,723 8/7/2023
0.7.1.31-preview 204 8/2/2023
0.7.1.30-preview 158 8/2/2023
0.7.1.29-preview 211 8/1/2023
0.7.1 668 8/3/2023
0.7.0.28-preview 128 7/28/2023
0.7.0.27-preview 235 7/28/2023
0.7.0.26-preview 212 7/27/2023
0.7.0.23-preview 196 7/27/2023
0.7.0.22-preview 248 7/27/2023
0.7.0.21-preview 186 7/27/2023
0.7.0.20-preview 230 7/27/2023
0.7.0 384 7/31/2023
0.6.3 1,143 7/22/2023
0.6.2 10,335 6/22/2023
0.6.1 302 6/20/2023
0.6.0 669 6/15/2023
0.5.30 453 6/12/2023
0.5.29 113 6/12/2023
0.5.28 126 6/10/2023
0.5.27 177 5/24/2023
0.5.26 252 5/11/2023
0.5.25 165 5/10/2023
0.5.3 172 5/5/2023
0.5.2 138 5/2/2023
0.5.1 135 5/2/2023
0.5.0 182 4/28/2023
0.4.2 172 4/24/2023
0.4.1 396 4/3/2023
0.4.0 277 3/24/2023
0.3.17 4,376 3/24/2023
0.3.16 222 3/22/2023
0.3.4 185 3/22/2023
0.3.3 272 3/17/2023
0.3.2 240 3/16/2023
0.3.1 215 3/14/2023
0.3.0 204 3/14/2023
0.2.4-alpha 225 3/1/2023
0.2.3-alpha 133 2/27/2023
0.2.2-alpha 176 2/25/2023
0.2.1-alpha 201 2/25/2023
0.2.0-alpha 168 2/24/2023
0.1.5-alpha 206 2/18/2023
0.1.4-alpha 248 2/17/2023
0.1.3-alpha 229 2/17/2023
0.1.2-alpha 198 2/17/2023