Strongly-Typed API Client Library built on Refit
A powerful, flexible API connector library that provides strongly-typed HTTP clients with automatic authentication, smart caching, rate limiting, request deduplication, automatic pagination, and OpenTelemetry integration. Supports Token, API Key, Basic, and full OAuth 2.0 authentication flows.
dotnet add package Noundry.Connector
Token, API Key, Basic Auth, and full OAuth 2.0 (Client Credentials, Auth Code + PKCE, Device Code, Password).
ETag and Last-Modified validation, configurable expiration, LRU eviction, and automatic invalidation on mutations.
Token bucket algorithm with sliding window, 429 retry handling, concurrent request limits, and per-endpoint configuration.
Page-based, offset, cursor, and Link header pagination styles with IAsyncEnumerable streaming.
Identical in-flight requests share a single HTTP call. Reduces duplicate API calls automatically.
Built-in metrics (request duration, response size, status codes) and distributed tracing with W3C propagation.
using Refit;
public interface IMyApi
{
[Get("/users")]
Task<List<User>> GetUsersAsync(CancellationToken ct = default);
[Get("/users/{id}")]
Task<User> GetUserAsync(int id, CancellationToken ct = default);
[Post("/users")]
Task<User> CreateUserAsync([Body] User user, CancellationToken ct = default);
}
using Noundry.Connector.Extensions;
// Add authentication
services.AddTokenAuthentication("your-api-token");
// Register the API client
services.AddConnector<IMyApi>(options =>
{
options.BaseUrl = "https://api.example.com";
options.MaxRetries = 3;
options.UseExponentialBackoff = true;
});
public class UserService
{
private readonly IMyApi _api;
public UserService(IMyApi api) => _api = api;
public async Task<List<User>> GetActiveUsersAsync()
{
var users = await _api.GetUsersAsync();
return users.Where(u => u.IsActive).ToList();
}
}
// Bearer Token
services.AddTokenAuthentication("your-bearer-token");
// API Key (header or query parameter)
services.AddApiKeyAuthentication("your-api-key");
services.AddApiKeyAuthentication("your-key", "api_key", ApiKeyLocation.QueryParameter);
// Basic Auth
services.AddBasicAuthentication("username", "password");
// OAuth 2.0 Client Credentials
services.AddOAuthAuthentication(oauth =>
{
oauth.ClientId = "client-id";
oauth.ClientSecret = "client-secret";
oauth.TokenEndpoint = "https://auth.example.com/oauth/token";
oauth.Scope = "api:read api:write";
oauth.GrantType = "client_credentials";
});
var config = OAuthConfiguration.ForAuthorizationCode(
clientId: "my-client",
authorizationEndpoint: "https://auth.example.com/authorize",
tokenEndpoint: "https://auth.example.com/token",
redirectUri: "https://myapp.com/callback",
scope: "openid profile",
usePkce: true);
var provider = new OAuthAuthenticationProvider(httpClient, config);
// Step 1: Redirect user
var authUrl = provider.GetAuthorizationUrl();
// Step 2: Exchange code for token
var token = await provider.ExchangeCodeForTokenAsync(code, state);
services.AddConnector<IMyApi>(options =>
{
options.BaseUrl = "https://api.example.com";
options.Timeout = TimeSpan.FromSeconds(30);
// Retry Policy (Polly)
options.MaxRetries = 3; // 0 = disabled
options.RetryDelay = TimeSpan.FromSeconds(1);
options.UseExponentialBackoff = true; // 2^n + RetryDelay
// Circuit Breaker
options.EnableCircuitBreaker = true;
options.CircuitBreakerThreshold = 5; // Failures before open
options.CircuitBreakerDuration = TimeSpan.FromSeconds(30);
// Custom headers
options.DefaultHeaders["X-Custom"] = "value";
});
services.AddHttpClient<IMyApi>()
.AddConnectorFeatures(options =>
{
options.EnableCaching = true;
options.EnableRateLimiting = true;
options.EnableDeduplication = true;
options.EnableTelemetry = true;
options.ConfigureCaching = cache =>
{
cache.UseETag = true;
cache.DefaultExpiration = TimeSpan.FromMinutes(5);
cache.MaxCacheSize = 500;
};
options.ConfigureRateLimiting = rate =>
{
rate.MaxRequestsPerSecond = 10;
rate.MaxConcurrentRequests = 5;
rate.RespectRetryAfterHeader = true;
};
});
Transparently fetch all pages using IAsyncEnumerable. Supports Page-based, Offset-based, Cursor-based, and Link Header styles.
using Noundry.Connector.Pagination;
var client = new PaginatedClient(httpClient);
// GitHub-style Link header pagination
var allRepos = await client
.GetAllWithLinkHeaderAsync<Repository>("/users/octocat/repos")
.ToListAsync();
// Cursor-based pagination (Slack, Twitter style)
var messages = await client
.GetAllWithCursorAsync<Message>(
"/conversations.history",
cursorParam: "cursor",
nextCursorPath: "$.response_metadata.next_cursor")
.TakeAsync(100)
.ToListAsync();
// Batch processing
await foreach (var batch in client.GetAllAsync<Order>("/orders").BatchAsync(100))
{
await ProcessBatchAsync(batch);
}
Identical in-flight GET requests automatically share a single HTTP call:
var task1 = api.GetUserAsync(1); // Makes HTTP request
var task2 = api.GetUserAsync(1); // Joins task1
var task3 = api.GetUserAsync(1); // Joins task1
await Task.WhenAll(task1, task2, task3);
// Only ONE HTTP request was made, all three get the same result
ConnectorExceptionBase exception for all Connector errors.
AuthenticationExceptionAuthentication failures (invalid token, expired credentials).
HttpExceptionHTTP errors with StatusCode and ResponseContent properties.
ValidationExceptionValidation errors with field-level error details.
ConfigurationExceptionConfiguration errors (missing base URL, invalid options).
RateLimitExceededExceptionQueue full or timeout waiting for a rate limit slot.
Automatically generate strongly-typed API clients from any REST API:
# Install the generator
dotnet tool install -g Noundry.Connector.Cli
# Generate a complete API client (interactive wizard)
connector-gen generate
# The wizard guides you through:
# 1. API base URL and connectivity check
# 2. Authentication method selection
# 3. Endpoint discovery (OpenAPI)
# 4. Model generation from JSON responses
# 5. Output: Models, Refit interfaces, DI extensions
[Headers("User-Agent: MyApp/1.0", "Accept: application/vnd.github.v3+json")]
public interface IGitHubApi
{
[Get("/users/{username}")]
Task<GitHubUser> GetUserAsync(string username, CancellationToken ct = default);
[Get("/users/{username}/repos")]
Task<List<GitHubRepository>> GetUserReposAsync(
string username,
[Query] string? sort = null,
[Query("per_page")] int pageSize = 30,
CancellationToken ct = default);
}
// Registration with all features
services.AddTokenAuthentication(Environment.GetEnvironmentVariable("GITHUB_TOKEN")!);
services.AddConnector<IGitHubApi>(options =>
{
options.BaseUrl = "https://api.github.com";
options.MaxRetries = 3;
options.EnableCircuitBreaker = true;
});