ReqKey.AspNetCore 0.1.0

dotnet add package ReqKey.AspNetCore --version 0.1.0
                    
NuGet\Install-Package ReqKey.AspNetCore -Version 0.1.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="ReqKey.AspNetCore" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ReqKey.AspNetCore" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="ReqKey.AspNetCore" />
                    
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 ReqKey.AspNetCore --version 0.1.0
                    
#r "nuget: ReqKey.AspNetCore, 0.1.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 ReqKey.AspNetCore@0.1.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=ReqKey.AspNetCore&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=ReqKey.AspNetCore&version=0.1.0
                    
Install as a Cake Tool

ReqKey .NET SDK

CI

The official .NET SDK for ReqKey API key validation, credit metering, consumer rate limits, and correlated request analytics.

The implementation follows the ReqKey Python SDK's API contract and failure semantics while using normal .NET patterns: HttpClient, async methods, cancellation tokens, strongly typed options, dependency injection, middleware, Minimal API conventions, and MVC endpoint metadata.

Packages and supported applications

Package Purpose
ReqKey Framework-neutral client, API key extraction, validation, credit balance, analytics models, configuration, retry policy, and exceptions.
ReqKey.AspNetCore DI registration, ASP.NET Core middleware, Minimal API conventions, and MVC attributes.

Both projects target .NET 8. The ASP.NET package supports ASP.NET Core Minimal APIs, MVC, Web API controllers, and middleware-based applications. Classic ASP.NET is intentionally not included.

The projects contain NuGet metadata and can already be packed. Until the first NuGet release, reference the projects directly. After publication, installation will be:

dotnet add package ReqKey
dotnet add package ReqKey.AspNetCore

Plain C# client

Set the server-side project credential:

export REQKEY_PROJECT_KEY="your_project_key"

Then create one client around a reusable HttpClient:

using ReqKey;

using var httpClient = new HttpClient();
var client = new ReqKeyClient(
    httpClient,
    ReqKeyClientOptions.FromEnvironment());

var decision = await client.ValidateAsync(
    "consumer_key_...",
    apiId: "api_payments",
    credits: 2,
    resource: "/payments",
    cancellationToken);

if (!decision.Allowed)
{
    Console.WriteLine($"Denied: {decision.Reason}");
}

VerifyAsync is an alias for ValidateAsync. Validation atomically validates the key and deducts the configured credits. A zero-credit validation is a free check.

Read the consumer's shared credit pool through a key:

var balance = await client.GetCreditsAsync("consumer_key_...", cancellationToken);
Console.WriteLine(balance.IsUnlimited ? "unlimited" : balance.Remaining);

Send analytics directly:

await client.IngestAsync(new IngestEvent
{
    RequestId = decision.RequestId,
    ApiId = "api_payments",
    Method = "POST",
    Endpoint = "/payments",
    Path = "/payments?merchant=acme",
    StatusCode = 201,
    LatencyMilliseconds = 42,
    ApiKey = "consumer_key_...",
    ResponseBody = "{\"created\":true}",
}, cancellationToken);

Ingestion requires RequestId, ApiId, or both. Request and response bodies are truncated to 1,000 characters before transmission.

ASP.NET Core setup

Install or reference ReqKey.AspNetCore, then register the SDK and add its middleware after routing:

using ReqKey.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddReqKey(options =>
{
    options.ProjectKey = builder.Configuration["REQKEY_PROJECT_KEY"];
    options.ApiId = "api_payments";
    options.Mode = ReqKeyMode.Both;
    options.KeyName = "X-StartupName-Key";
    options.Credits = 1;
    options.ExcludePaths = ["/health", "/docs/*"];
});

var app = builder.Build();
app.UseRouting();
app.UseReqKey();

app.MapPost("/payments", (HttpContext context) => Results.Ok(new
{
    created = true,
    creditsRemaining = context.GetReqKeyDecision()?.CreditsRemaining,
}));

The same options can come from an IConfiguration section:

builder.Services.AddReqKey(builder.Configuration.GetSection("ReqKey"));

Example appsettings.json values:

{
  "ReqKey": {
    "ProjectKey": "use-an-environment-variable-in-production",
    "ApiId": "api_payments",
    "Mode": "Both",
    "KeyLocation": "Header",
    "KeyName": "X-API-Key",
    "KeyScheme": "Raw",
    "Credits": 1,
    "Timeout": "00:00:02",
    "FailureMode": "Closed"
  }
}

The project key must remain server-side. Prefer the standard ASP.NET Core environment configuration provider rather than storing it in committed JSON.

Middleware lifecycle

With Mode = ReqKeyMode.Both, middleware performs this lifecycle:

incoming request
  -> extract consumer key
  -> await /key/validate
  -> denied: optionally await /ingest, return 401 / 402 / 403 / 429
  -> allowed: execute the endpoint
  -> collect selected response metadata
  -> await /ingest with the validation requestId
  -> complete the request

Validate only gates traffic. Ingest only records traffic and does not require a consumer key. Both validates and creates correlated analytics. Denied requests are ingested by default in Both mode; set IngestDeniedRequests = false to disable that behavior.

API key extraction

Custom raw header, recommended:

options.KeyLocation = ApiKeyLocation.Header;
options.KeyName = "X-StartupName-Key";
options.KeyScheme = ApiKeyScheme.Raw;

Authorization Bearer token:

options.KeyLocation = ApiKeyLocation.Header;
options.KeyName = "Authorization";
options.KeyScheme = ApiKeyScheme.Bearer;

Query parameter:

options.KeyLocation = ApiKeyLocation.Query;
options.KeyName = "api_key";

Cookies are also supported with ApiKeyLocation.Cookie. Query and cookie keys must use the raw scheme. Headers are safer because URLs are commonly retained in logs, browser history, and proxy telemetry.

A custom asynchronous resolver can integrate another trusted source:

options.ConsumerKeyResolver = (context, cancellationToken) =>
    ValueTask.FromResult<string?>(context.Request.Headers["X-Custom-Key"]);

Do not consume the request body to find an API key; doing so can interfere with endpoint model binding.

Credit costs and endpoint identification

Set a global static cost with Credits, or resolve it per request:

options.CreditCostResolver = (context, cancellationToken) =>
    ValueTask.FromResult(
        context.Request.Method == HttpMethods.Post && context.Request.Path == "/images"
            ? 5
            : 1);

Minimal APIs can override cost, API identification, and analytics resource on one endpoint:

app.MapPost("/images", HandleImage)
    .RequireReqKey(credits: 5, apiId: "api_images", resource: "/v1/images");

app.MapGet("/health", HandleHealth).SkipReqKey();

MVC and Web API controllers use the equivalent attributes:

[HttpPost]
[ReqKey(Credits = 2, ApiId = "api_orders", Resource = "/v1/orders")]
public IActionResult CreateOrder() => Ok();

[HttpGet("health")]
[SkipReqKey]
public IActionResult Health() => Ok();

These declarations are endpoint metadata consumed by the shared middleware; they do not duplicate validation or analytics logic.

Route selection

Exact paths and trailing-wildcard prefixes can bypass ReqKey:

options.ExcludePaths = [
    "/health",
    "/openapi.json",
    "/docs/*",
    "/cron/*",
];

OPTIONS is skipped by default. Replace or add to SkipMethods as needed. For full control:

options.ShouldProtect = (context, cancellationToken) =>
    ValueTask.FromResult(context.Request.Path.StartsWithSegments("/api"));

The same selection controls validation and ingestion. Bypassed traffic is not validated, charged, or recorded.

Analytics and privacy

The middleware sends these fields by default:

  • validation requestId, when available;
  • apiId, HTTP method, normalized endpoint, response status, and latency;
  • user agent;
  • the extracted consumer key in the dedicated apiKey field so ReqKey can resolve the consumer internally.

Additional capture is opt-in:

options.CaptureQueryParameters = true;
options.CaptureRequestHeaders = true;
options.CaptureResponseHeaders = true;
options.CaptureResponseBody = true;
options.CaptureClientIp = true;
options.ExcludedHeaders = ["X-RapidAPI-Proxy-Secret"];

Authorization, cookies, Set-Cookie, proxy authorization, common API key headers, the configured consumer-key header, and custom excluded headers are always removed from captured header dictionaries. When the key comes from the query string, it is removed from both QueryParameters and the captured path.

Response-body capture only keeps a streaming prefix and never buffers the full response. Only textual content is decoded, and the final value is limited to 1,000 characters. Automatic request-body capture is intentionally omitted.

Client-IP capture prefers HttpContext.Connection.RemoteIpAddress. When no peer address exists, common forwarding headers are parsed. For a trusted proxy, use ClientIpResolver to identify the exact header your proxy overwrites. Never trust caller-controlled forwarding headers without a trusted proxy boundary.

An optional consumer display name can be attached to analytics:

options.ConsumerNameResolver = (context, cancellationToken) =>
    ValueTask.FromResult<string?>(context.Request.Headers["X-RapidAPI-User"]);

Request state and response headers

After successful validation, endpoints can read:

VerificationResult? decision = HttpContext.GetReqKeyDecision();
string? requestId = HttpContext.GetReqKeyRequestId();

During fail-open operation, HttpContext.GetReqKeyError() exposes the service error that caused validation to be bypassed.

Responses include available decision metadata:

  • X-ReqKey-Request-ID
  • X-ReqKey-Credits-Limit
  • X-ReqKey-Credits-Remaining
  • X-ReqKey-Validation-Time-Ms
  • Retry-After for rate-limited denials

Cross-origin browser JavaScript must add any headers it needs to read to its CORS WithExposedHeaders(...) configuration.

Failure behavior and custom responses

Validation fails closed by default. A timeout, transport failure, invalid project credential, or unexpected ReqKey response returns 503 without running the endpoint.

options.FailureMode = ReqKeyFailureMode.Open;

Fail-open applies only to ReqKey service failures. Invalid, exhausted, forbidden, and rate-limited consumer keys are always denied.

Customize built-in messages by stable error code:

options.ErrorMessages[ReqKeyErrorCodes.MissingApiKey] =
    "Send your key in X-StartupName-Key.";
options.ErrorMessages[ReqKeyErrorCodes.InsufficientCredits] =
    "Your account has no credits remaining.";

For complete response control, set OnDenied. The middleware sets the status code and passes a ReqKeyDenial; the callback writes the response:

options.OnDenied = async (context, denial, cancellationToken) =>
{
    await context.Response.WriteAsJsonAsync(new
    {
        code = denial.ErrorCode,
        detail = denial.Message,
    }, cancellationToken);
};

Service failures can be forwarded to an alerting system without exposing credentials or request content:

options.OnError = async (errorEvent, cancellationToken) =>
{
    await alerts.SendAsync(new
    {
        operation = errorEvent.Operation,
        errorEvent.Message,
        errorEvent.Method,
        errorEvent.Path,
        errorEvent.RequestId,
        errorEvent.StatusCode,
    }, cancellationToken);
};

Callback failures are logged and do not replace the application response.

Timeouts, retries, and logging

Each HTTP attempt has a two-second timeout by default:

options.Timeout = TimeSpan.FromSeconds(3);

Automatic retries are off until MaxRetries is greater than zero:

options.Retry.MaxRetries = 2;
options.Retry.BaseDelay = TimeSpan.FromMilliseconds(100);
options.Retry.MaxDelay = TimeSpan.FromSeconds(1);
options.Retry.UseJitter = true;

Ingestion and credit-balance reads are retryable when retries are enabled. Validation is not retried by default because it can deduct credits. Enable it only when your deployment has an idempotency guarantee:

options.Retry.RetryValidationRequests = true;

Explicit validation decisions such as 402, 403, and 429 are never retried. Cancellation requested by the caller is propagated as OperationCanceledException; SDK attempt timeouts raise ReqKeyTimeoutException.

The SDK uses Microsoft.Extensions.Logging and never logs keys, captured headers, query parameters, or bodies. Set EnableLogging = false to suppress SDK diagnostic messages.

Exceptions

All SDK failures derive from ReqKeyException:

Exception Meaning
ReqKeyConfigurationException Missing or invalid SDK input.
ReqKeyTransportException ReqKey could not be reached.
ReqKeyTimeoutException An attempt exceeded Timeout.
ReqKeyApiException ReqKey returned a non-decision or malformed response.
ReqKeyAuthenticationException ReqKey rejected the project credential.

Invalid consumer keys, insufficient credits, forbidden access, and rate limits are normal VerificationResult decisions rather than exceptions.

Examples

Build, test, and pack

dotnet restore ReqKey.sln
dotnet build ReqKey.sln --configuration Release --no-restore
dotnet test ReqKey.sln --configuration Release --no-build

dotnet pack src/ReqKey/ReqKey.csproj --configuration Release
dotnet pack src/ReqKey.AspNetCore/ReqKey.AspNetCore.csproj --configuration Release

The test suite covers API key extraction, validation results, credit usage, analytics payloads, retry behavior, errors, timeouts, middleware behavior, redaction, endpoint overrides, and dependency injection.

Project layout

sdk-dotnet/
├── src/
│   ├── ReqKey/                 # framework-neutral core
│   └── ReqKey.AspNetCore/      # ASP.NET Core integration
├── tests/ReqKey.Tests/
├── examples/
├── ReqKey.sln
└── README.md
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 was computed.  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 was computed.  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
0.1.0 38 7/23/2026