Skip to content

Refit

Web & HTTPNetworking/HTTPC#

What it is

Refit turns a C# interface into a REST client, generating the HTTP calls from attributes at build time with no reflection at runtime.

Declare an interface with HTTP attributes. Refit generates the implementation, handling serialisation, query strings and headers.

Installation

dotnet add package Refit

Getting started

The smallest useful thing you can do with it, and what each part means.

Interface as client
public interface IBookApi
{
    [Get("/books/{id}")]
    Task<Book> GetBookAsync(int id, CancellationToken ct = default);

    [Get("/books")]
    Task<List<Book>> SearchAsync([Query] string title, [Query] int? year = null);

    [Post("/books")]
    Task<Book> CreateAsync([Body] CreateBookRequest request);

    [Headers("Authorization: Bearer")]
    [Delete("/books/{id}")]
    Task DeleteAsync(int id, [Header("Authorization")] string token);
}

builder.Services
    .AddRefitClient<IBookApi>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"))
    .AddStandardResilienceHandler();   // Polly, for free
Registering through HttpClientFactory gives correct connection lifetime handling and lets Polly resilience layer on without touching the interface.

Advanced usage

Where the library earns its place over a simpler alternative.

Error handling and typed failures
// Returning ApiResponse<T> avoids exceptions for expected failures.
[Get("/books/{id}")]
Task<ApiResponse<Book>> TryGetBookAsync(int id);

var response = await api.TryGetBookAsync(42);
if (!response.IsSuccessStatusCode)
{
    if (response.StatusCode == HttpStatusCode.NotFound) return null;
    logger.LogError("api error {Status} {Content}",
        response.StatusCode, response.Error?.Content);
}

// Otherwise, non-2xx throws ApiException.
try { await api.GetBookAsync(42); }
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
ApiResponse<T> is the better choice when a 404 is an ordinary outcome — exceptions for expected control flow are both slow and noisy in logs.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

ApiException on a 404
Expected. Either catch it with a status filter, or return ApiResponse<T> and check IsSuccessStatusCode.
Socket exhaustion under load
A new HttpClient per call. Always register through HttpClientFactory.

Best practices

  • Register via AddRefitClient so HttpClientFactory manages connection lifetimes.
  • Use ApiResponse<T> for endpoints where non-2xx responses are expected.
  • Accept a CancellationToken on every method and pass it through.
  • Layer Polly resilience on the registration rather than inside the interface.

Background

Why it exists, and what it was reacting to.

Modelled on Square's Retrofit for Java, Refit replaced hand-written HttpClient wrappers with a declarative interface, and uses a source generator so there is no runtime cost.