Gamers Lab Docs

4.1 Tenant API Keys (X-API-Key)

Section 4 — API & Game Key Management

4.1 Tenant API Keys (X-API-Key)

Tenant API Keys are issued by tenant owners or admins to allow external systems to read data from Gamers Lab. They are passed in the X-API-Key request header.

What they can do:

  • Read match history
  • Read leaderboards
  • Read event records
  • Read tenant logs
  • Subscribe to streaming endpoints (if enabled)
  • Access is controlled via allow/deny flags, live scope, and data profiles (field exclusion, PII masking)
  • A four-stage middleware pipeline enforces: use-case → read-only → rate limit → post-response usage tracking

They cannot write game data. The only non-read methods allowed for tenant API keys are the explicit allowAuth authentication endpoints.

Security characteristics:

  • Stored as key_hash (never plaintext)
  • Flagged (allow_data_api, allow_live_events, allow_active_match_data, live_events_scope)
  • Access shaped by profiles (LiveEventsProfileId, HistoricalDataProfileId) that control field visibility and PII masking
  • Rate limited (Redis Lua-based atomic minute/hour counters)
  • Can be revoked
  • Can expire

Subscriber keys (subtype of Tenant API Keys): Subscriber keys are still tenant API keys, but they are issued to end users (subscribers) and tracked separately for billing and access control.

  • Stored in bus_tenant_api_keys with created_by_type = 'subscriber' and created_by_user_id
  • Subscription state lives in bus_tenant_subscriptions
  • Same read-only guarantees, auth POST allowlist, and flags as other tenant API keys

Key count limits:

  • Default max read keys (API keys): 3 per tenant.
  • Per-tenant overrides are supported. Platform admins can bypass count limits.

Read key rate limits (per key):

  • Default: 60 requests/minute, 1,000 requests/hour.
  • Derived from tenant/company plan. Subscriber keys use subscriber plan limits when set.
  • Per-key limits are capped by the plan ceiling.

Rate limit behavior:

  • Exceeding limits returns HTTP 429 with Retry-After and X-RateLimit-Reset headers.
  • Read rate limiting is fail-open by default, except for live/streaming paths which are fail-closed.

Key management:

  • Keys can be rotated, revoked, or deleted.
  • Rate limits are adjusted via plan changes, not per-key edits.
  • Key names: 1–100 characters. Descriptions: up to 500 characters.

4.1.1 Key Lifecycle

A tenant API key is a read-only credential used by dashboards, analytics tools, and subscriber integrations to query game data. Unlike player JWTs or game auth keys, it carries no session and does not expire by inactivity — it is active until explicitly rotated, revoked, or deleted.

The secret value is shown once at creation or rotation. The platform stores only a SHA-256 hash of the key and never exposes the plaintext again after that single response.

Key format: Keys use the prefix sk_live_ followed by cryptographically random bytes. A short prefix (the first visible characters) is stored alongside the hash so keys can be identified in the UI without revealing the secret.

StoredNot stored
SHA-256 hash of the full keyFull plaintext key
Key prefix (for display)

Key fields:

FieldDefaultMutableNotes
nameYesDisplay name; 1–100 chars
descriptionYesOptional context; max 500 chars
expiresAtnullNoOptional expiry; null = never expires
allowDataApitrueYesAccess to historical data query endpoints
allowAuthfalseYesAllows player JWT validation and hosted third-party auth calls
allowLiveEventsfalseYesAccess to SSE live event streams
allowActiveMatchDatatrueYesAccess to active (in-progress) match data
liveEventsScopeallYesnone, all, self, or team
liveEventsProfileIdnullYesProfile controlling field exclusions for SSE
historicalDataProfileIdnullYesProfile controlling field visibility for historical queries
hostedAuthAppNamenullYesOptional display/app name for hosted third-party auth
hostedAuthRedirectUris[]YesAllowed redirect URIs for hosted third-party auth flows
corsAllowedOrigins[]YesAllowed browser origins for hosted auth/API-key browser use
rateLimitPerMinute60NoSet at creation; not configurable per-key post-create
rateLimitPerHour1000NoSet at creation; not configurable per-key post-create
isActivetrueYesCan be toggled via update
isPublicfalseNoReserved for subscriber/public keys

Use-case flags:

Every key has four boolean flags that gate which endpoint categories it can access. The middleware rejects requests to any category the key does not permit.

FlagDefaultGates access to
allowDataApitrueAll historical data query endpoints
allowAuthfalsePlayer JWT validation and hosted third-party auth endpoints (/start, /token, /refresh)
allowLiveEventsfalseServer-Sent Events (SSE) live streams
allowActiveMatchDatatrueActive match data (in-progress matches)

A key with all flags false would authenticate successfully but be rejected at the use-case enforcement stage for every endpoint.

Lifecycle operations: All operations require a SaaS JWT with owner or admin role on the tenant.

OperationEffect
CreateGenerates a new key; plaintext returned once; hash stored
ListReturns all keys for the tenant; plaintext never included
GetReturns a single key by ID; prefix and metadata only
UpdateUpdates mutable fields (name, description, flags, profiles, hosted auth/CORS settings, isActive)
RotateGenerates a new secret in-place on the same key ID; old secret immediately invalidated; new plaintext returned once
RevokeSoft-deactivates the key — sets isActive = false, records revokedAt and revokedBy; row is retained for audit history. Re-enabling through update clears revoked metadata.
DeleteHard-deletes the key row from the database; no record remains

Rotate vs Revoke vs Delete:

RotateRevokeDelete
Key still works afterNo — immediatelyNoNo
Row retainedYesYesNo
revokedAt setNoYes
New key issuedYes — same IDNoNo
Use casePeriodic credential refreshDeactivation with audit trailClean removal (e.g. created in error)

Limits:

LimitValue
Max active keys per tenant3
Default rate limit per minute60
Default rate limit per hour1,000
name length100 chars
description length500 chars

The 3-key cap applies per tenant. Attempting to create a fourth key returns an error. Revoked keys still count toward the limit if the row is retained; use delete to free the slot.

Cache and invalidation: Validated keys are cached in Redis to avoid a database lookup on every request. Rotation and revocation both invalidate the cache entry immediately, so the old secret stops working as soon as the operation completes — there is no window where the old key remains valid after rotation.

Audit logging: All write operations (create, update, rotate, revoke, delete) are recorded in the tenant audit log. Tenant-owned key rotation is an in-place secret replacement on the same key ID. Subscriber key rotation creates a replacement row and links it to the previous row via rotatedFromId.

Limitations:

  • Rate limits are fixed at creation. rateLimitPerMinute and rateLimitPerHour are not updatable after creation. To change rate limits, rotate or recreate the key.
  • Plaintext is shown once. If the key value is lost after creation or rotation, the only recovery is to rotate the key and store the new value.
  • expiresAt is stored but expiry is checked at validation time — there is no background job that auto-revokes expired keys.

Code references:

  • Controller: GamersLabRestAPI/Saas/ApiKeys/Controllers/ApiKeysController.cs
  • Service: GamersLabRestAPI/Saas/ApiKeys/Services/ApiKey/ApiKeyService.cs
  • Key generator: GamersLabRestAPI/Saas/ApiKeys/Services/ApiKey/ApiKeyGenerator.cs
  • Repository: GamersLabRestAPI/Saas/ApiKeys/Repositories/NpgsqlApiKeyRepository.cs
  • Model: GamersLabRestAPI/Saas/ApiKeys/Models/TenantApiKey.cs
  • Auth handler: GamersLabRestAPI/Saas/ApiKeys/Services/ApiKey/ApiKeyAuthenticationHandler.cs

4.1.2 Live Events Profiles

A live events profile is a JSON configuration that controls which events are streamed and how they are presented to a caller connected to a Server-Sent Events (SSE) endpoint. Every SSE connection resolves the effective profile for the requesting API key and applies its rules to each outbound event before it is written to the stream.

The API key must have allowLiveEvents = true to access SSE stream endpoints. The profile is referenced via liveEventsProfileId on the key.

Profiles are stored in bus_live_events_profiles. Each row holds the profile name, description, a profile_json blob containing the full configuration, and an is_system flag that protects platform-defined profiles from modification or deletion.

System profiles: Four system profiles are pre-defined and available to all tenants. They cannot be updated or deleted.

ProfileID suffixDefault behavior
All…0101Full visibility — all stream types delivered to all callers
Team…0102Match-scope types (matchStart, matchEnd, matchResults, matchEvents) use team visibility; player-scope types (playerEvents, sessionLogins, sessionLogouts) use self visibility
Self…0103All stream types restricted to the authenticated player's own data only
None…0104No events streamed — all types blocked

When no profile is configured for an API key and no tenant default is set, the platform selects the fallback system profile based on the API key's liveEventsScope setting (all, team, self, or none). This differs from historical data profiles, which always fall back to All.

Profile configuration fields: Every profile JSON object must include version (must be 1) and enabled (bool). All other fields are optional.

FieldTypeDescription
versionintMust be 1. Required.
enabledboolIf false, no events are streamed regardless of other settings. Required.
visibilityByType{ streamType: visibilityLevel }Per-type visibility overrides. Omitted types default to all.
delaysSeconds{ streamType: int }Seconds to delay delivery per stream type. Must be ≥ 0. Omitted types have no delay.
identityRedactionobjectGlobal identity redaction applied to every streamed event.
identityRedactionByType{ streamType: redactionObject }Per-type identity redaction; merged with global.
includeHostByType[ streamType ]Stream types for which the match host's data is always included.
overridesobjectEvent allow/deny filter lists.

Stream types: The 7 recognized stream type keys:

KeyDescription
matchStartEmitted when a match transitions to started state
matchEndEmitted when a match transitions to ended state
matchResultsMatch outcome records published at match end
matchEventsIn-match game events as they are recorded
playerEventsPlayer-scoped game events
sessionLoginsPlayer session login events
sessionLogoutsPlayer session logout events

Visibility levels: Used in visibilityByType. Stream types not listed in the map default to all.

ValueMeaning
noneStream type is completely blocked — no events of this type are sent
allEvents delivered regardless of caller identity
matchCaller must be an authenticated participant in the match
registeredCaller must be a registered (authenticated) player
teamCaller must share the same team as the event subject
selfCaller must be the specific player the event belongs to
hostOnly the match host receives events of this type

Levels that require a player identity (self, team, match, registered, host) require the SSE connection to carry a valid player token. Connections using only an X-API-Key without a player token will not receive those stream types.

Delivery delays: delaysSeconds is a { streamType: int } map. When set for a stream type, the platform holds matching events for the specified number of seconds before writing them to the SSE stream. Values must be ≥ 0; negative values are rejected at save time.

Identity redaction: Identity redaction strips identifying fields from streamed event payloads.

  • identityRedaction — applies globally to every stream type.
  • identityRedactionByType — applies per stream type; merged with the global setting. Redaction can only be made more restrictive by overrides, never relaxed.

The four redactable fields for live events:

FieldWhat it hides
playerIdThe player's platform UUID
matchPlayerIdThe player's match-scoped UUID
teamIdThe team UUID
teamLabelThe human-readable team name

Live events identity redaction has a smaller surface than historical data profiles (4 fields vs 8) — session and device identifiers are not present in SSE payloads.

Event override lists: The overrides object controls which events are included in event-carrying stream types (matchEvents, playerEvents). All lists are case-insensitive and deduplicated on save.

FieldEffect
allowEventKeysInclude only events whose eventKey is in this list. An empty or absent list means all keys are allowed.
denyEventKeysExclude events whose eventKey is in this list. Applied after allowEventKeys.
allowEventCategoriesInclude only events whose definition category is in this list.
denyEventCategoriesExclude events whose definition category is in this list.
allowEventValuesInclude only events whose eventValue is in this list.
denyEventValuesExclude events whose eventValue is in this list.

Allow lists act as inclusive filters (only matching records pass). Deny lists are always evaluated after allow lists and always exclude matching records regardless of other settings.

Profile resolution order: For each SSE connection, the platform resolves the effective profile in this order:

  1. The liveEventsProfileId on the API key (if set).
  2. The tenant's default live events profile (stored in bus_tenants.default_live_events_profile_id).
  3. The scope-mapped system profile based on the API key's liveEventsScope: none → None, self → Self, team → Team, all (or unset) → All.

If the resolved profile row cannot be found (e.g. deleted after assignment), the platform logs a warning and falls back to the scope-mapped system profile.

Override merging for subscriber keys: Subscriber and public API keys can carry additional profile overrides in their metadata (ApiKeyProfileOverrides.LiveEvents). When present, these overrides are merged on top of the resolved base profile:

  • Overrides can only make visibility more restrictive, never more permissive.
  • Identity redaction fields can only be enabled by an override, not disabled.
  • Invalid override entries are logged as warnings and skipped.

Available operations: All operations require a SaaS JWT with owner or admin role on the tenant.

OperationNotes
ListReturns all profiles including the 4 system profiles; flags isDefault on the tenant's current default
GetReturns a single profile by ID; 404 if not found or not scoped to this tenant
CreateCreates a new tenant profile; version and enabled required; all stream type keys, visibility values, and delay values are validated
UpdateUpdates name, description, and/or profile JSON; system profiles cannot be updated
DeleteHard-deletes the profile row; system profiles cannot be deleted; all active API key caches for the tenant are invalidated immediately
Set DefaultSets the tenant's default profile; applies to all API keys that have no explicit profile assigned

Caching: Two Redis cache entries are maintained per profile resolution (5-minute TTL each): live-events-profile:effective:{apiKeyId} and live-events-profile:tenant-default:{tenantId}. Both are invalidated on create, update, delete, and set-default operations.

Limitations:

  • System profiles are immutable.
  • Fallback depends on the key's scope setting — unlike historical data profiles (which always fall back to All), live events profiles fall back to the scope-mapped system profile.
  • Version must be 1.
  • Delay cannot be negative.
  • 4 redactable fields only — session, device, and IP identifiers are not available in SSE payloads.
  • Overrides can only restrict; subscriber key overrides cannot relax a base profile.
  • 5-minute cache lag.

Code references:

  • Controller: GamersLabRestAPI/Saas/ApiKeys/Controllers/LiveEventsProfilesController.cs
  • Service: GamersLabRestAPI/Saas/ApiKeys/Services/LiveEventsProfileService.cs
  • Model: GamersLabRestAPI/Saas/ApiKeys/Models/LiveEventsProfile.cs
  • Stream types: GamersLabRestAPI/Saas/Business/Constants/LiveEventStreamTypes.cs
  • Live events evaluator: GamersLabRestAPI/Game/Data/Services/LiveEventsProfileEvaluator.cs
  • Override evaluator: GamersLabRestAPI/Saas/ApiKeys/Services/ProfileOverrideEvaluator.cs

4.1.3 Historical Data Profiles

A historical data profile is a JSON configuration attached to a tenant that controls which fields and data types are visible when a caller queries historical game data through read endpoints (e.g. match details, player stats, event records). Every historical data query resolves the effective profile for the requesting API key and applies its rules before returning data.

The API key must have allowDataApi = true to access historical data query endpoints. The profile is referenced via historicalDataProfileId on the key.

Profiles are stored in bus_historical_data_profiles. Each row holds the profile name, description, a profile_json blob, and an is_system flag that protects platform-defined profiles from modification or deletion.

System profiles: Four system profiles are pre-defined and available to all tenants. They cannot be updated or deleted.

ProfileID suffixDefault behavior
All…0201Full visibility — all fields returned for all data types
Team…0202Restricts visibility to team-level — a caller sees their own team's data and aggregated match data
Self…0203Restricts visibility to the authenticated player's own data only
None…0204No data returned — all types blocked

When no profile is configured for an API key and no tenant default is set, the platform falls back to the All system profile.

Profile configuration fields: Every profile JSON object must include version (must be 1) and enabled (bool). All other fields are optional.

FieldTypeDescription
versionintMust be 1. Required.
enabledboolIf false, the profile returns none for every data type regardless of other settings. Required.
visibilityByType{ dataType: visibilityLevel }Per-type visibility overrides. Omitted types default to all.
identityRedactionobjectGlobal identity redaction applied to every response.
identityRedactionByType{ dataType: redactionObject }Per-type identity redaction; merged with global.
includeHostByType[ dataType ]Data types for which the host player's data is included even when visibility would normally exclude it.
overridesobjectEvent allow/deny filter lists.
historicalOptionsobjectAdditional options; currently delayTillMatchCompletedByType.

Data types: The 14 recognized data type keys:

KeyDescription
matchListPaginated list of matches
matchDetailFull match metadata and result
matchPlayersPlayer roster within a match
matchResultsMatch outcome records
matchEventsIn-match event records
playerEventsPlayer-scoped event records
sessionLoginsSession login records
sessionLogoutsSession logout records
timelineChronological match/player/session timeline. V2 is cursor-paged and omits device, platform, client version, and IP fields.
playerSummaryAggregated player statistics
playerMatchesA player's match history
playerLookupPlayer identity resolution
playerStatsDetailed player stats
leaderboardsRanked leaderboard results

Visibility levels: Used in visibilityByType. Types not listed in the map default to all.

ValueMeaning
noneData type is completely blocked — endpoint returns no records for this type
allFull data returned regardless of caller identity
matchCaller must be an authenticated participant in the match
registeredCaller must be a registered (authenticated) player
teamCaller must share the same team as the subject
selfCaller must be the specific player whose data is requested
hostOnly the match host can access this data

Levels that require a player identity require the request to carry a valid player token. Requests using only an X-API-Key without a player token will be denied access to those data types.

Identity redaction: Removes or hashes identifying fields from responses.

  • identityRedaction — applies globally to every data type.
  • identityRedactionByType — applies per data type; merged with the global setting. Redaction can only be made more restrictive by overrides, never relaxed.

The eight redactable fields:

FieldWhat it hides
playerIdThe player's platform UUID
matchPlayerIdThe player's match-scoped UUID
teamIdThe team UUID
teamLabelThe human-readable team name
loginSessionIdThe session UUID
sessionIdInternal session tracking ID
deviceIdDevice fingerprint
ipAddressClient IP address

Event override lists: The overrides object controls which event records appear in event-returning data types (matchEvents, playerEvents). Same allow/deny list semantics as live events profiles — allow lists act as inclusive filters; deny lists are evaluated after allow lists.

Delay until match completed: historicalOptions.delayTillMatchCompletedByType is a { dataType: bool } map. When set to true for a data type, records of that type are withheld until the match is in a completed state.

Include host by type: includeHostByType is an array of data type keys. For listed types, the match host's data is always returned even when the visibility level would otherwise exclude it.

Profile resolution order:

  1. The historicalDataProfileId on the API key (if set).
  2. The tenant's default profile (stored in bus_tenants.default_historical_data_profile_id).
  3. The All system profile (open access, no restrictions).

If the resolved profile row cannot be found, the platform logs a warning and falls back to the All system profile.

Override merging for subscriber keys: Same merging rules as live events profiles — overrides can only restrict, not relax. Invalid entries are logged and skipped.

Available operations: All operations require a SaaS JWT with owner or admin role on the tenant.

OperationNotes
ListReturns all profiles including the 4 system profiles; flags isDefault on the tenant's current default
GetReturns a single profile by ID; 404 if not found or not scoped to this tenant
CreateCreates a new tenant profile; version and enabled required; all type keys and visibility values are validated
UpdateUpdates name, description, and/or profile JSON; system profiles cannot be updated
DeleteHard-deletes the profile row; system profiles cannot be deleted; all active API key caches for the tenant are invalidated immediately
Set DefaultSets the tenant's default profile; applies to all API keys that have no explicit profile assigned

Caching: Two Redis cache entries (5-minute TTL each): historical-data-profile:effective:{apiKeyId} and historical-data-profile:tenant-default:{tenantId}. Both invalidated on create, update, delete, and set-default.

Limitations:

  • System profiles are immutable.
  • Fallback to All on missing profiles — if an API key references a deleted profile, the platform silently falls back to All. No error is returned to the caller.
  • Version must be 1.
  • Overrides can only restrict.
  • No per-field visibility — visibility is controlled at the data-type level, not individual JSON field level. Use identity redaction for identity fields, event override lists for event records.
  • 5-minute cache lag.

Code references:

  • Controller: GamersLabRestAPI/Saas/ApiKeys/Controllers/HistoricalDataProfilesController.cs
  • Service: GamersLabRestAPI/Saas/ApiKeys/Services/HistoricalDataProfileService.cs
  • Model: GamersLabRestAPI/Saas/ApiKeys/Models/HistoricalDataProfile.cs
  • Data types: GamersLabRestAPI/Core/Constants/HistoricalDataTypes.cs
  • Historical evaluator: GamersLabRestAPI/Game/Data/Services/HistoricalDataProfileEvaluator.cs

Profile Type Comparison

Both profile systems share the same architecture and operations. The differences are in scope, type vocabulary, and a few feature-specific fields.

Historical Data ProfilesLive Events Profiles
DB tablebus_historical_data_profilesbus_live_events_profiles
Applies toRead/query endpointsSSE live event streams
Key flag requiredallowDataApiallowLiveEvents
Key fieldhistoricalDataProfileIdliveEventsProfileId
Type vocabulary14 data types7 stream types

Feature comparison:

FeatureHistoricalLive Events
version requiredYes — must be 1Yes — must be 1
enabled requiredYesYes
visibilityByTypeYes — 14 data typesYes — 7 stream types
Identity redaction (global)Yes — 8 fieldsYes — 4 fields
Identity redaction (per type)YesYes
includeHostByTypeYesYes
Event allow/deny override listsYesYes
Delivery delaydelayTillMatchCompletedByType (bool per type)delaysSeconds (int seconds per type, ≥ 0)
Scope-aware fallbackNo — always falls back to AllYes — falls back to system profile matching liveEventsScope on the key
Subscriber key overridesYesYes
Override merging rulesMore restrictive onlyMore restrictive only

Identity redaction field coverage:

Historical data profiles expose more redactable fields because query responses include session and device data that SSE payloads do not.

FieldHistoricalLive Events
playerIdYesYes
matchPlayerIdYesYes
teamIdYesYes
teamLabelYesYes
loginSessionIdYes
sessionIdYes
deviceIdYes
ipAddressYes

System profile IDs:

ProfileHistorical ID suffixLive Events ID suffix
All…0201…0101
Team…0202…0102
Self…0203…0103
None…0204…0104

Both use a 5-minute Redis TTL. Cache keys follow the same pattern with different prefixes. Operations (List, Get, Create, Update, Delete, Set Default) are identical for both and require a SaaS JWT with owner or admin role.

4.1.4 Rate Limiting

Per-key quota enforcement using Redis Lua scripts for atomic increment and check. Minute and hour counters with configurable limits. Returns 429 with Retry-After and X-RateLimit-Reset headers on exceeded limits.

Read key rate limits (per key):

  • Default: 60 requests/minute, 1,000 requests/hour.
  • Derived from tenant/company plan. Subscriber keys use subscriber plan limits when set.
  • Per-key limits are capped by the plan ceiling.

Rate limit behavior:

  • Exceeding limits returns HTTP 429 with Retry-After and X-RateLimit-Reset headers.
  • Read rate limiting is fail-open by default, except for live/streaming paths which are fail-closed.
  • Rate limits are adjusted via plan changes, not per-key edits.

4.1.5 Usage Tracking & Analytics

Every request authenticated with an X-API-Key is tracked by ApiKeyUsageTrackingMiddleware. Recording happens post-response — after the full pipeline has executed and the response has been written — so it has no impact on response latency from the caller's perspective.

Fields recorded per request to bus_api_key_usage:

FieldDescription
endpointRoute template (e.g. api/bus_tenants/{tenantId}/matches/{id})
pathActual request path
methodHTTP method
request_kindread (GET/HEAD/OPTIONS) or write (all others)
status_codeHTTP response status code
response_time_msElapsed milliseconds from request start to response completion
request_bytesContent-Length of the request body (if present)
response_bytesContent-Length of the response body (if present)
ip_addressClient IP address
user_agentUser-Agent header value
is_throttledtrue if the request was rate-limited (429)
quota_bucketWhich quota bucket the request was charged against
billing_outcomeHow the request was classified for billing
billable_unitsNumber of units charged (0 for non-accepted outcomes)
billed_workspace_idCompany or subscriber workspace the usage is attributed to
occurred_atUTC timestamp of the request

Tracking can be suppressed for specific endpoints by setting HttpContextItemKeys.SkipApiKeyUsageTracking = true in HttpContext.Items.

Billing classification: Each recorded request is assigned a quota bucket and a billing outcome by UsageBillingClassifier.

Quota buckets:

BucketWhen used
tenant_read_requestsStandard X-API-Key GET/query requests
subscriber_read_requestsRequests made by subscriber or public keys
tenant_write_requestsGame key write/ingest requests
live_connect_creditsSSE live event connection establishment
live_connection_minutesSSE live event connection duration

Billing outcomes:

OutcomeMeaningBillable units
acceptedRequest succeeded (2xx/3xx)≥ 1
rejectedRequest failed (4xx/5xx, not throttled, not malformed)0
deniedRequest was rate-limited (429)0
malformedRequest was a 400 Bad Request0
idempotent_replayRequest was a duplicate of a previously processed write0

Only accepted requests consume billable units.

Analytics queries: All analytics endpoints require a SaaS JWT with owner or admin role on the tenant. Default window: last 30 days. Pagination: default 100, max 500.

Per-key analytics response includes:

SectionContents
statisticsTotal requests, requests in last 24h / 7d / 30d, average response time, success/failure counts, first and last used timestamps
endpointUsagePer-endpoint breakdown: request count, average response time, success and error counts
hourlyUsageTime series: request count, average response time, error count — one bucket per hour for the last 24 hours
dailyUsageTime series: request count, average response time, error count — one bucket per day for the last 30 days
statusCodeDistributionMap of HTTP status code → request count
topIpAddressesRanked list of calling IPs: request count, last seen timestamp

Tenant-wide analytics response includes:

SectionContents
overallStatisticsSame fields as per-key statistics, aggregated across all keys
apiKeyUsagePer-key summary: key name, prefix, total requests, requests last 24h, last used, active status
dailyTrendDaily time series for the tenant as a whole

Daily usage returns a flat day-by-day breakdown (default last 90 days), optionally filtered to a single key:

FieldDescription
dateUTC day
readCountNumber of read (GET) requests
writeCountNumber of write requests
readBytesTotal request body bytes for reads
writeBytesTotal request body bytes for writes

Limitations:

  • Post-response only. If the process crashes after writing the response but before the tracking call completes, that request's record is lost. Usage records are best-effort, not transactional.
  • response_bytes depends on Content-Length. Streamed or chunked responses without a Content-Length header will have a null response_bytes value.
  • rows_returned is not auto-populated. The field exists but is only set by controllers that explicitly write it to HttpContext.Items.
  • No raw log access. The analytics endpoints return pre-aggregated views. Individual request records in bus_api_key_usage are not exposed through the API.

Code references:

  • Middleware: GamersLabRestAPI/Saas/ApiKeys/Middleware/ApiKeyUsageTrackingMiddleware.cs
  • Billing classifier: GamersLabRestAPI/Saas/Quota/Services/UsageBillingClassifier.cs
  • Analytics controller: GamersLabRestAPI/Saas/ApiKeys/Controllers/ApiKeyAnalyticsController.cs
  • Analytics service: GamersLabRestAPI/Saas/ApiKeys/Services/UsageAnalyticsService.cs
  • Usage model: GamersLabRestAPI/Saas/ApiKeys/Models/ApiKeyUsage.cs

4.1.6 Middleware Pipeline

[NO DEDICATED DOC]

The API key middleware pipeline enforces three inbound stages for every X-API-Key request, then records usage after the response is produced:

  1. Use-case enforcement — checks the key's boolean flags (allowDataApi, allowAuth, allowLiveEvents, allowActiveMatchData) against the requested endpoint's required use-case. A key that is missing the required flag is rejected even if it is otherwise valid and active.

  2. Read-only enforcement — rejects non-read HTTP methods (POST, PUT, PATCH, DELETE) from a tenant API key except for the explicit hosted-auth allowlist: player JWT validation and third-party auth start/token/refresh endpoints. Those auth endpoints still require allowAuth = true.

  3. Rate limit enforcement — checks the per-key Redis Lua atomic counters for the minute and hour windows. Returns 429 with Retry-After and X-RateLimit-Reset headers if the limit is exceeded. Fail-open by default (except live/streaming paths which are fail-closed).

  4. Post-response usage tracking — after the response has been written, records the request to bus_api_key_usage with billing classification. This stage runs post-response and has no effect on response latency.

The pipeline is implemented as ASP.NET middleware components. HttpContext.Items is used to pass state between stages (quota bucket, billing outcome, skip-tracking flag).

Code references:

  • Auth handler: GamersLabRestAPI/Saas/ApiKeys/Services/ApiKey/ApiKeyAuthenticationHandler.cs
  • Usage tracking middleware: GamersLabRestAPI/Saas/ApiKeys/Middleware/ApiKeyUsageTrackingMiddleware.cs
  • Middleware helper: GamersLabRestAPI/Core/Middleware/UsageTrackingMiddlewareHelper.cs

On this page