laravel-bir-regon

← Documentation

Request limits

GUS applies request limits to all API operations, including authentication and GetValue. Laravel BIR REGON coordinates that budget locally before opening the HTTP connection. The Laravel service provider enables CacheBirRequestLimiter by default.

The limiter is a safety mechanism, not a reservation of capacity at GUS. Calls made with the same key by an application that does not share the limiter state remain invisible to this package, and GUS may still reject a request.

Official schedule

The package selects the published GUS BIR limits using the Europe/Warsaw time zone:

Local time Requests per second Requests per minute Requests per hour
06:00–07:59 and 17:00–21:59 3 150 8,000
08:00–16:59 3 120 6,000
22:00–05:59 4 200 10,000

These values come from the GUS BIR 1.2 documentation published on the official REGON API portal. Review the portal when planning high-volume workloads because the service owner may change its policy.

What consumes the budget

The native transport reserves quota after it has validated and built the local request, immediately before each real SOAP call. Login, logout, every GetValue, a full report, and a bulk report each cost one unit. A search costs one unit for every identifier in its SearchCriteria:

Authentication and recovery calls are included. For example, a search without an active session normally costs one login unit plus its identifier count. getFullReports() first performs that search and then spends one additional unit for every distinct compatible report target it fetches. Session diagnostics, a replacement login, and a retried operation consume additional units when the session expires.

The limiter allows one valid batch to create short-term quota debt. A later operation is paced only within the bounds described below; otherwise it is rejected until that debt is repaid. Do not run chunks of 20 in a tight loop without handling backoff.

Enforcement model

The package applies a conservative local model; it does not claim to reproduce an undocumented server-side GUS algorithm:

The fixed minute and hour windows fail fast and are never paced. Within a new or external acquire() call, second-level pacing is limited to one second. NativeBirGateway::callForRecords() additionally opens a logical rate-limit scope. Before its first successful reservation the same one-second limit applies; after that reservation, each subsequent internal acquire() in the same scope may pace for up to seven seconds. This lets diagnostics, a new login, and one retry finish after an accepted search or report. A recovery sequence contains several acquisitions and can therefore sleep for more than seven seconds in total.

Every pacing sleep happens after releasing the cache lock. Scope management is explicit through beginRateLimitScope() and endRateLimitScope(); it is not a closure callback.

Laravel configuration

The available environment variables are:

BIR_RATE_LIMIT_ENABLED=true
BIR_RATE_LIMIT_STORE=redis
BIR_RATE_LIMIT_PREFIX=bir-regon:rate-limit
Variable Default Purpose
BIR_RATE_LIMIT_ENABLED true Uses the cache-backed limiter in the Laravel integration; accepts only the exact booleans true or false
BIR_RATE_LIMIT_STORE The application’s default cache store Selects a named Laravel cache store
BIR_RATE_LIMIT_PREFIX bir-regon:rate-limit Separates package state from other cache keys

CacheBirRequestLimiter accepts only the exact base Illuminate\Cache\Repository, without tags, decorators, or repository subclasses, backed by one of these explicitly supported Laravel stores:

Implementing LockProvider alone is not sufficient. TaggedCache, repository decorators and subclasses, DynamoDB, FailoverStore, MemoizedStore, NullStore, and custom stores fail closed with BirRateLimitException. This avoids pairs of cache and lock semantics that have not been verified together. An application with another backend must provide its own BirRequestLimiterInterface implementation and pass it to NativeSoapTransport, or replace the transport.

The published rate_limit.enabled value must resolve to a PHP boolean. Laravel parses the standard unquoted dotenv values true and false accordingly. Empty, numeric, quoted, or otherwise malformed values raise LogicException instead of being coerced into an accidental opt-out.

The atomic lock has a 30-second lease. Acquisition waits for contention for at most one second, verifies ownership around the state write, and fails closed rather than sending an uncoordinated request. The lock is not held while the limiter sleeps.

Use one shared Redis store for queue workers or application instances on multiple hosts. Every host must use the same backend and database plus the same effective namespace for state and lock connections, including the Redis client (REDIS_PREFIX), Laravel cache (CACHE_PREFIX), and BIR prefixes. ArrayStore and FileStore are local and do not coordinate separate hosts. A database or Memcached store coordinates only when every process really uses the same backend. The state identity contains the environment and a SHA-256 fingerprint of the API key, never the raw credential, so production, sandbox, and different API keys have independent budgets.

After changing these values in a cached application, rebuild its configuration cache:

php artisan config:cache

Bounded pacing and fail-fast backoff

When second-level debt exceeds the applicable one- or seven-second pacing bound, or a minute/hour window blocks the request, the limiter throws BirRateLimitException before network I/O. A queued job can release itself for the calculated delay:

use cieplik206\BirRegon\Exceptions\BirRateLimitException;
use cieplik206\BirRegon\Facades\BirRegon;

try {
    $companies = BirRegon::forNip($nip)->get();
} catch (BirRateLimitException $exception) {
    $this->release($exception->retryAfterSeconds());

    return;
}

quotaWasExceeded() is true when the local model blocks the request. retryAfterSeconds() rounds up the longest active second, minute, or hour blocker, plus any delay needed after the wall clock moved backwards. It is always at least one second. When coordination itself is unavailable, quotaWasExceeded() is false and retryAfterSeconds() is one second. Lock contention can already have consumed up to one second before that result. The exception propagates without triggering new session diagnostics.

Disabling BIR_RATE_LIMIT_ENABLED makes the Laravel provider use UnlimitedBirRequestLimiter. This is appropriate only when another component coordinates every caller using the key. Otherwise it removes local protection.

Direct and custom transports

NativeSoapTransport constructed directly requires an explicit BirRequestLimiterInterface; the cache-backed default is supplied only by the Laravel service provider. Use UnlimitedBirRequestLimiter only as a conscious opt-out when another layer coordinates every caller using the credential.

A replacement BirGatewayInterface or BirSoapTransportInterface owns its outbound calls and therefore also owns rate limiting. Use UnlimitedBirRequestLimiter or a recording fake in isolated tests where no network call is possible, and inject a rejecting fake when testing backoff behavior. Custom limiter implementations must repeat #[\SensitiveParameter] on parameter arrays that can carry search identifiers; interface parameter attributes are not inherited.

Continue with Service status, diagnostics, and logout.