PHPackages                             reqkey/reqkey - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. reqkey/reqkey

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

reqkey/reqkey
=============

Official PHP SDK for ReqKey API key validation, credit metering, and request analytics

v0.1.0(yesterday)10MITPHPPHP ^8.1CI passing

Since Jul 23Pushed yesterdayCompare

[ Source](https://github.com/Req-Key/reqkey-php)[ Packagist](https://packagist.org/packages/reqkey/reqkey)[ Docs](https://reqkey.com)[ RSS](/packages/reqkey-reqkey/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (24)Versions (2)Used By (0)

ReqKey PHP SDK
==============

[](#reqkey-php-sdk)

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

The package contains one framework-neutral client and middleware engine, plus small adapters for plain PHP, PSR-15, Laravel, Symfony, and Slim 4 (through its PSR-15 support). PHP 8.1 or later is required.

Install
-------

[](#install)

```
composer require reqkey/reqkey
```

Set the server-side project credential and API identifier:

```
REQKEY_PROJECT_KEY=reqkey_project_...
REQKEY_API_ID=api_payments
```

`REQKEY_ROOT_KEY` remains supported as a backward-compatible alias. Never put the project credential in browser code or commit it to source control.

Direct client
-------------

[](#direct-client)

```
use ReqKey\Client;

$client = Client::fromEnvironment();
$decision = $client->verify(
    key: 'consumer_key_...',
    apiId: 'api_payments',
    credits: 2,
    resource: '/payments',
);

if (!$decision->allowed()) {
    // $decision->reason, statusCode, message, retryAfter, and raw are available.
}

echo $decision->creditsRemaining;
```

Validation atomically charges the configured credits. Access decisions are returned as `VerificationResult` values. Project authentication failures, timeouts, transport failures, and unexpected API responses throw typed exceptions under `ReqKey\Exception`.

Direct analytics ingestion uses a typed event:

```
use ReqKey\Model\AnalyticsEvent;

$client->ingest(new AnalyticsEvent(
    requestId: $decision->requestId,
    apiId: 'api_payments',
    method: 'POST',
    endpoint: '/payments',
    statusCode: 201,
    latencyMs: 42,
    apiKey: 'consumer_key_...',
));
```

`requestId`, `apiId`, or both must be present. Request and response bodies are hard-limited to 1,000 characters.

Shared middleware setup
-----------------------

[](#shared-middleware-setup)

All adapters use the same processor. A manual setup looks like this:

```
use ReqKey\Client;
use ReqKey\Config\ClientConfig;
use ReqKey\Config\MiddlewareConfig;
use ReqKey\Enum\Mode;
use ReqKey\MiddlewareProcessor;

$client = new Client(ClientConfig::fromEnvironment());
$processor = new MiddlewareProcessor(
    $client,
    new MiddlewareConfig(
        apiId: 'api_payments',
        mode: Mode::Both,
        keyName: 'X-Product-Key',
        credits: 1,
        excludePaths: ['/health', '/docs/*'],
    ),
);
```

With `Mode::Both`, the lifecycle is:

```
consumer request
  -> extract consumer key
  -> validate and charge credits
  -> denied: record denial and return 401/402/403/429
  -> allowed: run application handler
  -> collect selected response metadata
  -> ingest a correlated analytics event

```

Ingestion is completed inside the request lifecycle. Errors sending analytics are reported and logged but never replace the application response.

Plain PHP
---------

[](#plain-php)

`ReqKeyGuard` reads the native request globals while keeping all authorization logic in the shared processor:

```
use ReqKey\Integration\PlainPhp\ReqKeyGuard;

$guard = new ReqKeyGuard($processor);
$result = $guard->begin();

if (!$result->allowed()) {
    $guard->emitDenial($result);
    return;
}

// Run the application and then report its response metadata.
$guard->complete($result, statusCode: 200);
```

See [`examples/plain-php.php`](examples/plain-php.php) for a complete script.

PSR-15
------

[](#psr-15)

The adapter implements `Psr\Http\Server\MiddlewareInterface` and uses the included Guzzle PSR-7 factories by default. Custom response and stream factories may be injected.

```
use ReqKey\Integration\Psr15\ReqKeyMiddleware;

$middleware = new ReqKeyMiddleware($processor);
// Add $middleware to the framework's PSR-15 middleware queue.
```

After validation, handlers can read:

```
$decision = $request->getAttribute('reqkey');
$requestId = $request->getAttribute('reqkey.request_id');
```

In fail-open mode, the transport exception is available as `$request->getAttribute('reqkey.error')`.

See [`examples/psr15.php`](examples/psr15.php).

Slim 4
------

[](#slim-4)

Slim uses the same PSR-15 adapter; no Slim-specific validation logic is needed:

```
$app->addRoutingMiddleware();
$app->add(new ReqKey\Integration\Psr15\ReqKeyMiddleware($processor));
```

See [`examples/slim.php`](examples/slim.php) for a runnable application.

Laravel
-------

[](#laravel)

The package is discoverable by Laravel. Publish its configuration:

```
php artisan vendor:publish --tag=reqkey-config
```

Set at least:

```
REQKEY_PROJECT_KEY=reqkey_project_...
REQKEY_API_ID=api_payments
```

The service provider registers the client, shared processor, and a `reqkey`middleware alias. Apply it globally, to a group, or to selected routes:

```
Route::middleware('reqkey')->post('/payments', function (Request $request) {
    $decision = $request->attributes->get('reqkey');

    return response()->json([
        'created' => true,
        'credits_remaining' => $decision->creditsRemaining,
    ], 201);
});
```

Edit `config/reqkey.php` to set capture, failure, key-location, or retry options. For dynamic credit costs or custom resolvers, place closures in the middleware configuration after publishing it:

```
'credits' => static fn (ReqKey\Http\RequestData $request): int =>
    $request->method === 'POST' && $request->path === '/images' ? 5 : 1,
```

The original `Illuminate\Http\Request` is available as `$request->nativeRequest` inside every resolver. The complete route and env examples are in [`examples/laravel`](examples/laravel).

Symfony
-------

[](#symfony)

Register the bundle when the project does not use a Flex recipe:

```
// config/bundles.php
return [
    ReqKey\Integration\Symfony\ReqKeyBundle::class => ['all' => true],
];
```

Configure it in `config/packages/reqkey.yaml`:

```
reqkey:
  project_key: '%env(REQKEY_PROJECT_KEY)%'
  timeout: 2.0
  middleware:
    api_id: '%env(REQKEY_API_ID)%'
    mode: both
    key_location: header
    key_name: X-API-Key
    credits: 1
    exclude_paths: ['/health', '/docs/*']
    failure_mode: closed
```

The bundle registers the client, configuration objects, shared processor, and event subscriber. Controllers read the decision from Symfony request attributes:

```
$decision = $request->attributes->get('reqkey');
```

See [`examples/symfony`](examples/symfony). Applications that prefer explicit service wiring can copy the supplied [`services.yaml`](src/Integration/Symfony/Resources/config/services.yaml).

API-key extraction
------------------

[](#api-key-extraction)

Headers are recommended because URLs are often retained in history and logs.

```
use ReqKey\Enum\KeyLocation;
use ReqKey\Enum\KeyScheme;

// Raw custom header (default pattern)
new MiddlewareConfig(
    apiId: 'api_payments',
    keyLocation: KeyLocation::Header,
    keyName: 'X-Product-Key',
);

// Authorization: Bearer consumer_key_...
new MiddlewareConfig(
    apiId: 'api_payments',
    keyName: 'Authorization',
    keyScheme: KeyScheme::Bearer,
);

// ?api_key=consumer_key_...
new MiddlewareConfig(
    apiId: 'api_payments',
    keyLocation: KeyLocation::Query,
    keyName: 'api_key',
);
```

Cookies are supported with `KeyLocation::Cookie`. A `keyResolver` closure may read a custom trusted source. Query and cookie credentials must use the raw scheme.

Dynamic credits and endpoint identification
-------------------------------------------

[](#dynamic-credits-and-endpoint-identification)

Credit cost can be static or resolved per request:

```
new MiddlewareConfig(
    apiId: 'api_payments',
    credits: static function (ReqKey\Http\RequestData $request): int {
        return $request->path === '/images' ? 5 : 1;
    },
    endpointResolver: static function (ReqKey\Http\RequestData $request): string {
        // A framework route template such as /users/{id} can be returned here.
        return $request->path;
    },
);
```

The resolved endpoint is sent as validation `resource` and analytics `endpoint`. Resolvers must return a non-negative integer and non-empty string, respectively.

Client configuration
--------------------

[](#client-configuration)

Constructor optionDefaultPurpose`projectKey`requiredServer-side ReqKey project credential.`rootKey``null`Backward-compatible credential alias; never pass both.`baseUrl``https://api.reqkey.com`ReqKey API origin.`timeout``2.0`Total timeout for one HTTP attempt, in seconds.`connectTimeout``2.0`Connection timeout, in seconds.`maxRetries``0`Additional attempts after the first.`retryDelayMs``100`Initial exponential-backoff delay.`retryMaxDelayMs``2000`Maximum delay, including `Retry-After`.`retryStatusCodes`408, 429, 500, 502, 503, 504HTTP responses eligible for retry.`retryValidation``false`Permit validation retries. See warning below.`retryIngest``true`Permit ingestion retries when `maxRetries > 0`.`logger``null`Any PSR-3 logger for retries and middleware failures.`ClientConfig::fromEnvironment()` recognizes `REQKEY_PROJECT_KEY`, `REQKEY_ROOT_KEY`, `REQKEY_BASE_URL`, `REQKEY_TIMEOUT`, `REQKEY_CONNECT_TIMEOUT`, `REQKEY_MAX_RETRIES`, `REQKEY_RETRY_DELAY_MS`, `REQKEY_RETRY_VALIDATION`, and `REQKEY_RETRY_INGEST`.

Validation may deduct credits, so it is not retried by default. Enable `retryValidation` only when your ReqKey deployment provides appropriate idempotency semantics. Ingestion is safe to retry, but retries still require `maxRetries` greater than zero.

A reusable `GuzzleHttp\ClientInterface` can be supplied to `GuzzleTransport`, or a custom `ReqKey\Transport\TransportInterface` can be injected into `ReqKey\Client`.

Middleware configuration
------------------------

[](#middleware-configuration)

OptionDefaultPurpose`apiId`requiredReqKey API to protect or observe.`mode``Mode::Both`Validate, ingest, or do both.`enabled``true`Bypass ReqKey entirely when false.`keyLocation``KeyLocation::Header`Header, query parameter, or cookie.`keyName``X-API-Key`Consumer-facing credential name.`keyScheme``KeyScheme::Raw`Raw value or Bearer header.`credits``1`Static cost or request resolver closure.`excludePaths``[]`Exact paths or trailing-`*` prefix patterns.`skipMethods``['OPTIONS']`Methods excluded from validation and analytics.`keyResolver``null`Custom API-key extraction.`shouldProtect``null`Full request-selection callback.`requestIdResolver``null`Correlation ID for ingest-only mode.`endpointResolver`request pathNormalized resource/endpoint identifier.`consumerNameResolver``null`Optional analytics display name.`clientIpResolver`peer addressOverride IP logic for a trusted proxy.`userIdResolver` / `consumerIdResolver``null`Optional analytics identities.`onError``null`Provider-neutral validation/ingestion failure callback.`ingestDeniedRequests``true`Record denied traffic in both mode.`failureMode``FailureMode::Closed`Deny or allow when ReqKey is unavailable.`errorMessages`built inOverride stable denial messages.`captureQueryParams``false`Include query parameters and query string.`captureRequestHeaders``false`Include filtered request headers.`captureResponseHeaders``false`Include filtered response headers.`captureRequestBody``false`Include up to 1,000 textual characters.`captureResponseBody``false`Include up to 1,000 textual characters.`captureClientIp``false`Include client IP.`captureUserAgent``true`Include user agent.`excludedHeaders``[]`Additional header names never captured.Resolvers receive `ReqKey\Http\RequestData`, which includes method, path, headers, query parameters, cookies, peer IP, and `nativeRequest`.

Analytics privacy
-----------------

[](#analytics-privacy)

Default analytics include the validation request ID, API ID, method, endpoint, status, latency, user agent, and extracted consumer key in the dedicated `apiKey` field used for consumer resolution. All broader capture is opt-in.

Authorization, proxy authorization, cookies, `Set-Cookie`, `X-API-Key`, the configured consumer-key header, and custom `excludedHeaders` are always removed from captured header maps. A credential read from a query parameter is removed from captured query data and the analytics path. Bodies are captured only for textual content types and are truncated. Non-seekable PSR streams are not consumed for analytics.

Client-IP capture prefers the framework peer address. If unavailable, it can parse common proxy headers. Only use `clientIpResolver` to trust a forwarding header when your deployment proxy overwrites that header.

Availability and error responses
--------------------------------

[](#availability-and-error-responses)

Explicit validation decisions always deny access:

ReasonHTTP statusError codeMissing or invalid key401`missing_api_key` / `invalid_api_key`Insufficient credits402`insufficient_credits`API access denied403`access_denied`Consumer rate limited429`rate_limited`ReqKey unavailable in fail-closed mode503`reqkey_unavailable`Fail-open applies only to ReqKey transport, timeout, authentication, or service errors. It never permits an explicit invalid, exhausted, forbidden, or rate-limited decision.

`onError` receives a `MiddlewareErrorEvent` containing operation, safe error text, method, path, request ID, and response status. It intentionally excludes credentials, headers, queries, and bodies. Errors thrown by the callback are logged and do not change the HTTP response.

Request state and response headers
----------------------------------

[](#request-state-and-response-headers)

Successful framework requests expose the `VerificationResult` as `reqkey` and the correlation ID as `reqkey.request_id`. Laravel and Symfony use request attributes; PSR-15 and Slim use PSR request attributes.

Responses include available metadata:

- `X-ReqKey-Request-ID`
- `X-ReqKey-Credits-Limit`
- `X-ReqKey-Credits-Remaining`
- `X-ReqKey-Validation-Time-Ms`

Cross-origin browser JavaScript must list these under the application's CORS `expose_headers` setting if it needs to read them.

Testing and development
-----------------------

[](#testing-and-development)

```
composer install
composer test
composer analyse
composer validate --strict
```

The test suite covers direct API contracts, credit costs, extraction, typed errors, timeouts and retries, availability behavior, analytics filtering, middleware responses, Laravel registration, Symfony services/subscribers, PSR-15, and Slim.

The package metadata, PSR-4 autoloading, license, source layout, and optional framework suggestions are ready for a Packagist repository. Packagist versions should be created from Git tags rather than a hard-coded Composer version.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance100

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity32

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/307492914?v=4)[ReqKey](/maintainers/Req-Key)[@Req-Key](https://github.com/Req-Key)

---

Top Contributors

[![soorower](https://avatars.githubusercontent.com/u/50005313?v=4)](https://github.com/soorower "soorower (1 commits)")

---

Tags

api-keysauthenticationlaravelphppsr-15sdksymfonysymfonylaravelAuthenticationpsr-15api keysusage-based-billing

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/reqkey-reqkey/health.svg)

```
[![Health](https://phpackages.com/badges/reqkey-reqkey/health.svg)](https://phpackages.com/packages/reqkey-reqkey)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k16](/packages/tempest-framework)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[cakephp/cakephp

The CakePHP framework

8.9k19.5M1.8k](/packages/cakephp-cakephp)[typo3/cms-core

TYPO3 CMS Core

3713.2M5.2k](/packages/typo3-cms-core)[sunrise/http-router

A powerful solution as the foundation of your project.

17451.8k11](/packages/sunrise-http-router)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M598](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
