SmooAI.Fetch 4.0.0

dotnet add package SmooAI.Fetch --version 4.0.0
                    
NuGet\Install-Package SmooAI.Fetch -Version 4.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SmooAI.Fetch" Version="4.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SmooAI.Fetch" Version="4.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SmooAI.Fetch" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SmooAI.Fetch --version 4.0.0
                    
#r "nuget: SmooAI.Fetch, 4.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SmooAI.Fetch@4.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SmooAI.Fetch&version=4.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SmooAI.Fetch&version=4.0.0
                    
Install as a Cake Tool

SmooAI.Fetch

NuGet License: MIT

HTTP that gets out of your way for .NET 8+ — typed JSON in and out, automatic retry on transient failures, auth token injection, one error type for every non-2xx.

.NET port of @smooai/fetch. Stop writing the same retry/backoff/auth wrapper for every API client in every service. Wire-compatible semantics with the TypeScript, Python, Go, and Rust ports.

Install

dotnet add package SmooAI.Fetch

What you get

  • Typed JSON — GetAsync<User> and PostAsync<Dto, User>. Your request and response shapes, strongly typed. No JsonSerializer.Deserialize boilerplate on every call site.
  • Automatic retries on transient failures, for requests that are safe to replay — network blips, timeouts, and 408 / 429 / 5xx responses are retried with exponential backoff + jitter for idempotent methods (GET, HEAD, OPTIONS, TRACE, PUT, DELETE). POST and PATCH are never retried unless you opt in — see Retries are method-aware.
  • Retry-After is honored — when a server tells you "wait 5s", the client waits 5s instead of your default backoff. Never eat a 429 again.
  • Async auth tokens — register an AuthTokenProvider once; every request picks up a fresh bearer token without restarting HttpClient.
  • One typed error per non-2xx — catch HttpResponseError and you've got status, body, headers, URI, and method on one exception.
  • Per-request cancellation + timeout — linked CancellationTokenSource under the hood; every method takes a CancellationToken.
  • DI-ready — AddSmooFetch(options => …) plugs into IHttpClientFactory and your IServiceCollection.

Quick start — standalone

using SmooAI.Fetch;

var fetch = SmooFetch.Create(options =>
{
    options.BaseUrl           = "https://api.example.com";
    options.Timeout           = TimeSpan.FromSeconds(30);
    options.RetryPolicy       = RetryPolicy.ExponentialBackoff(maxRetries: 3);
    options.AuthTokenProvider = async ct => await GetBearerTokenAsync(ct);
});

var me      = await fetch.GetAsync<User>("/users/me");
var created = await fetch.PostAsync<CreateUserDto, User>("/users", dto);

Quick start — DI / IHttpClientFactory

builder.Services.AddSmooFetch(options =>
{
    options.BaseUrl           = builder.Configuration["Api:BaseUrl"];
    options.RetryPolicy       = RetryPolicy.ExponentialBackoff(maxRetries: 3);
    options.AuthTokenProvider = _ => Task.FromResult<string?>(bearerToken);
});

// Inject wherever you need it
public class BillingService(SmooFetch fetch)
{
    public Task<Invoice> GetInvoice(string id) =>
        fetch.GetAsync<Invoice>($"/invoices/{id}");
}

Retry policy — honors Retry-After

options.RetryPolicy = RetryPolicy.ExponentialBackoff(
    maxRetries: 3,
    baseDelay:  TimeSpan.FromMilliseconds(250),
    maxDelay:   TimeSpan.FromSeconds(10));
  • Retries on transient exceptions (timeouts, socket errors) and on 408 / 429 / 500 / 502 / 503 / 504 — for retry-eligible requests only (below).
  • Honors the Retry-After header on 429 / 503 — if the server says "wait 5s", the client waits 5s instead of your backoff.
  • Exponential backoff with jitter; bounded by maxDelay.

Retries are method-aware

A POST that timed out, or came back 429 / 5xx, may already have run on the server. Sending it again can charge a card twice, send a message twice, or bill a second generated image. So a failed attempt is retried only when the request is retry-eligible:

  • its method is idempotent per RFC 9110 §9.2.2 — GET, HEAD, OPTIONS, TRACE, PUT, DELETE; or
  • the policy sets AllowNonIdempotent = true; or
  • the request carries a non-empty Idempotency-Key header (RetryPolicy.IdempotencyKeyHeader), i.e. the server has promised to de-duplicate replays.

An ineligible request makes exactly one attempt. OnRejection is not consulted, and the outcome surfaces as-is: the non-2xx response or the original exception. That includes a 429 with Retry-After on a POST: it is still not replayed unless you opt in. Eligibility is decided on the final request, after the AuthTokenProvider and PreRequest hook run, so a hook can add the Idempotency-Key. The in-process rate limiter is unaffected, because it waits before anything is sent.

// This endpoint is safe to replay: opt the whole client in.
options.RetryPolicy = RetryPolicy.Default with { AllowNonIdempotent = true };

// Or opt in one request, with a key the server de-duplicates on.
using var request = new HttpRequestMessage(HttpMethod.Post, "/payments") { Content = body };
request.Headers.Add(RetryPolicy.IdempotencyKeyHeader, paymentId);
using var response = await fetch.SendAsync(request);

Changed in 4.0.0. Through 3.x every method was retried, POST included.

Typed errors — one catch per layer

try
{
    var user = await fetch.GetAsync<User>("/users/me");
}
catch (HttpResponseError ex)
{
    // ex.StatusCode  — int
    // ex.Body        — string (response body)
    // ex.Headers     — HttpResponseHeaders
    // ex.RequestUri  — Uri
    // ex.Method      — HttpMethod
}

HttpResponseError is thrown for every non-2xx response. Transient exceptions (timeouts, socket resets) surface as their original type if retries are exhausted.

Async auth token provider

Auth is fetched per request via AuthTokenProvider: async ct => … — pair this with a cached/rotating token source and every call gets a fresh Authorization header without re-registering the client.

options.AuthTokenProvider = async ct =>
{
    var token = await _tokenCache.GetOrRefreshAsync(ct);
    return token; // null => no Authorization header on this request
};

Cancellation + per-request timeout

Every method accepts a CancellationToken. The configured Timeout applies per attempt: each attempt runs under its own CancellationTokenSource, linked to yours. When the timeout fires, HttpClient aborts the in-flight request and closes its connection before any retry starts, so the server sees the cancel. A timed-out attempt is never left running alongside its retry.

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var user = await fetch.GetAsync<User>("/users/me", cancellationToken: cts.Token);

License

MIT — © SmooAI

Product Compatible and additional computed target framework versions.
.NET 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.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.0.0 34 9/27/2026
3.7.1 108 8/28/2026
3.7.0 96 8/28/2026
3.6.2 140 8/20/2026
3.6.1 96 8/20/2026
3.6.0 98 8/20/2026
3.5.1 100 8/20/2026
3.5.0 103 8/20/2026
3.4.2 99 8/20/2026
3.4.1 113 8/20/2026
3.4.0 133 8/15/2026
3.3.10 146 5/18/2026
3.3.9 119 5/12/2026
3.3.8 107 5/12/2026
3.3.7 108 5/12/2026
3.3.6 118 5/12/2026
3.3.5 116 5/12/2026
3.3.4 117 4/24/2026