ReqKey.AspNetCore
0.1.0
dotnet add package ReqKey.AspNetCore --version 0.1.0
NuGet\Install-Package ReqKey.AspNetCore -Version 0.1.0
<PackageReference Include="ReqKey.AspNetCore" Version="0.1.0" />
<PackageVersion Include="ReqKey.AspNetCore" Version="0.1.0" />
<PackageReference Include="ReqKey.AspNetCore" />
paket add ReqKey.AspNetCore --version 0.1.0
#r "nuget: ReqKey.AspNetCore, 0.1.0"
#:package ReqKey.AspNetCore@0.1.0
#addin nuget:?package=ReqKey.AspNetCore&version=0.1.0
#tool nuget:?package=ReqKey.AspNetCore&version=0.1.0
ReqKey .NET SDK
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
apiKeyfield 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-IDX-ReqKey-Credits-LimitX-ReqKey-Credits-RemainingX-ReqKey-Validation-Time-MsRetry-Afterfor 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
examples/PlainConsole— direct client and credit balanceexamples/AspNetCoreMiddleware— global middlewareexamples/MinimalApi— endpoint metadata conventionsexamples/MvcWebApi— controller attributes
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 | Versions 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. |
-
net8.0
- ReqKey (>= 0.1.0)
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 |