Search by

beziworld / laravel-ceidg

kamcw

Laravel client for the official CEIDG API v3: look up Polish sole proprietorships by NIP or REGON, search the register, validate and watch business status

Package info

github.com/Beziworld/laravel-ceidg

Homepage

Issues

pkg:composer/beziworld/laravel-ceidg

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.0 2026-11-04 17:18 UTC

This package is auto-updated.

Last update: 2026-11-04 17:18:34 UTC


README

tests Latest Version License

Laravel client for the official API v3 of CEIDG (Centralna Ewidencja i Informacja o Działalności Gospodarczej), the Polish register of sole proprietorships, published by the Ministry of Development and Technology as the CEIDG data warehouse. Check a sole trader before you sign a contract or issue an invoice:

  • lookup by NIP or REGON, details by entry identifier, several entries in one request
  • owner, NIP and REGON with revoked or invalidated NIP flags, citizenships
  • status (AKTYWNY, ZAWIESZONY, WYKRESLONY, OCZEKUJE_NA_ROZPOCZECIE_DZIALANOSCI, WYLACZNIE_W_FORMIE_SPOLKI) as an enum, with the dates of start, suspension, resumption, end and removal
  • main, correspondence and additional business addresses with TERC, SIMC and ULIC codes
  • PKD codes, civil partnerships (NIP, REGON) of the entrepreneur, contact data and e-Delivery address
  • marital property community, bans, bankruptcy information, succession management, RDR permissions and restrictions
  • search with filters as a LazyCollection that reads further pages only while you iterate
  • identifiers of entries changed in a period, typed final readonly DTOs, the raw response for unmapped fields
  • caching, retries of read-only calls, rate-limit aware errors (RateLimited with retryAfter)
  • ActiveInCeidg validation rule, ceidg:show and ceidg:watch Artisan commands, CeidgStatusChanged event

Polska wersja dokumentacji

Why another CEIDG package

Existing packages on Packagist (netborg/laravel-ceidg-sdk, sigrun/ceidg-api, dominservice/ceidg-api) wrap the retired SOAP API v1 and target PHP 7 and Laravel 5/6; dominservice/ceidg_data_warehouse is a plain cURL client without Laravel integration. This package targets API v3 on PHP 8.3+ and Laravel 12/13, maps the whole documented response to typed objects and adds what an application needs around it: pagination, caching, error handling with the request limit in mind, validation and a status watcher.

Installation

composer require beziworld/laravel-ceidg
php artisan vendor:publish --tag=ceidg-config   # optional

Getting the free API token

  1. Sign in to biznes.gov.pl (trusted profile, e-ID or bank login).
  2. Register in the data warehouse at dane.biznes.gov.pl and request access to the API.
  3. The token (JWT, "klucz do API HD") arrives by e-mail. Access is free of charge.

The token is personal. Keep it in .env and never commit it.

CEIDG_TOKEN=eyJhbGciOi...
CEIDG_ENVIRONMENT=production   # or "test" for https://test-dane.biznes.gov.pl
CEIDG_CACHE_TTL=86400          # seconds; 0 disables caching
CEIDG_CACHE_STORE=             # cache store, empty = default store
CEIDG_RETRIES=2                # retries after connection errors and HTTP 5xx
CEIDG_TIMEOUT=30
CEIDG_WATCH=3563457932,2367852376   # NIP numbers checked by ceidg:watch
CEIDG_WATCH_STORE=             # persistent store for the last known statuses
CEIDG_URL=                     # overrides the endpoint of the environment

Lookup by NIP or REGON

use Beziworld\Ceidg\Facades\Ceidg;

$business = Ceidg::findByNip('356-345-79-32');   // separators and the "PL" prefix are accepted; null when not registered

$business->id;                       // "31F87519-9395-4FCF-8E19-6D5C0522FA7A"
$business->name;                     // "Adam IntegracjaMGMF Usługi Informatyczne"
$business->owner->name();            // "Adam IntegracjaMGMF"
$business->nip();                    // "3563457932"
$business->regon();                  // "618155359"
$business->owner->nipRevoked;        // false
$business->status;                   // BusinessStatus::Active
$business->status->label();          // "active"
$business->isActive();               // also isSuspended(), isDeleted(), isPending(), isOnlyInPartnership()
$business->startedAt;                // CarbonImmutable 2019-03-01
$business->suspendedAt;              // also resumedAt, endedAt, deletedAt
(string) $business->address;         // "ul. Zwierzyniecka 1/2, 15-333 Białystok"
$business->address->terc;            // "2061011"
$business->correspondenceAddress;    // Address or null
$business->additionalAddresses;      // list<Address>
$business->mainPkd->formatted();     // "62.01.Z"
$business->pkdCodes();               // ["6201Z", "6202Z"]
$business->partnerships;             // list<Partnership>: nip, regon, suspendedAt
$business->email;                    // also phone, website, eDeliveryAddress, otherContact
$business->propertyCommunity;        // PropertyCommunity::Yes, No or NotApplicable
$business->bans;                     // list<Ban>
$business->bankruptcies;             // list<Bankruptcy>
$business->successionManager;        // SuccessionManager or null
$business->permissions;              // list<RdrEntry>; restrictions as well
$business->raw;                      // decoded API object

One NIP can have several entries, for example a business closed years ago and a new one. findByNip() and findByRegon() return the most relevant entry (active first, then by status, then the most recently started); entriesByNip() and entriesByRegon() return all of them. A NIP or REGON with a wrong checksum throws InvalidIdentifier before any request.

Ceidg::company('31F87519-9395-4FCF-8E19-6D5C0522FA7A');        // by entry identifier; throws NotFound
Ceidg::companies([$id1, $id2]);                                 // several entries in one request

Search

use Beziworld\Ceidg\Enums\BusinessStatus;
use Beziworld\Ceidg\Query\SearchFilters;

$filters = SearchFilters::make()
    ->city('Białystok')
    ->pkd('62.01.Z')                             // dots are optional
    ->status(BusinessStatus::Active)
    ->startedBetween('2024-01-01', '2024-12-31')
    ->pageSize(50);

foreach (Ceidg::search($filters) as $business) {
    // pages are requested only while iterating
}

Ceidg::search(['lastName' => 'Kowalski', 'voivodeship' => 'podlaskie'])->take(10)->all();
Ceidg::search(['partnershipNip' => '8567773578'])->all();   // partners of a civil partnership

Available criteria: nip, regon, partnershipNip, partnershipRegon, firstName, lastName, name, street, building, flat, city, voivodeship, county, commune, postalCode, pkd, status, startedFrom, startedTo, pageSize (the API names such as miasto or dataod work too). Several values of one criterion are combined with OR, different criteria with AND. The API compares values case sensitively.

Search results carry the summary only (identifier, name, owner, main address, start date, status, isDetailed() is false); read the details with Ceidg::company($business->id).

Changed entries

Ceidg::changed('2026-11-02', '2026-11-03')->each(function (string $id) {
    $business = Ceidg::company($id);
});

The API recommends periods of at most five days.

Validation

use Beziworld\Ceidg\Rules\ActiveInCeidg;

$request->validate([
    'nip' => ['required', new ActiveInCeidg()],                                   // status AKTYWNY
    'partner_nip' => ['required', (new ActiveInCeidg())->allowPartnershipOnly()],  // also WYLACZNIE_W_FORMIE_SPOLKI
    'supplier_nip' => ['required', (new ActiveInCeidg())->allowSuspended()->strict()],
]);

The rule passes when the API cannot be reached or the request limit is exhausted, so an outage does not block your forms; strict() fails the validation instead. A rejected token throws AuthenticationFailed, because it is a configuration error. Companies registered in KRS are not in CEIDG; validate them with laravel-krs.

Status watcher

ceidg:watch reads the current entry of every watched NIP, compares its status with the status recorded on the previous run and dispatches Beziworld\Ceidg\Events\CeidgStatusChanged when it differs. The first run only records the statuses.

use Beziworld\Ceidg\CeidgWatcher;
use Beziworld\Ceidg\Events\CeidgStatusChanged;

// AppServiceProvider::boot(): watch the sole traders stored in the database
CeidgWatcher::watchUsing(fn () => Contractor::whereNotNull('nip')->pluck('nip'));

Event::listen(function (CeidgStatusChanged $event): void {
    $event->previous;            // BusinessStatus::Active
    $event->current;             // BusinessStatus::Suspended, or null when the entry disappeared
    $event->business;            // fresh Business or null
    $event->stoppedOperating();  // true
});
// routes/console.php
Schedule::command('ceidg:watch')->dailyAt('06:00');

Every watched NIP costs one request, so with the limit of 1000 requests per hour split large lists into batches (for example several resolvers run at different hours). Keep CEIDG_WATCH_STORE on a persistent store such as database or redis.

Artisan

php artisan ceidg:show 3563457932
php artisan ceidg:show 3563457932 --all            # every entry, including removed ones
php artisan ceidg:show 618155359 --regon
php artisan ceidg:show 31F87519-9395-4FCF-8E19-6D5C0522FA7A --id
php artisan ceidg:watch 3563457932 2367852376

Caching and limits

The API allows 1000 requests per 60 minutes per token. Details read by NIP, REGON or identifier are cached for CEIDG_CACHE_TTL seconds (one day by default); answers without data are not cached, so a newly registered business shows up immediately. Search results and change lists are not cached. Ceidg::forget($nipRegonOrId) clears one entry by hand; the watcher always reads fresh data.

HTTP 429 is never retried, because repeated requests may extend the block. It throws RateLimited with retryAfter in seconds when the gateway announces it (Retry-After or X-Rate-Limit-Reset). When the gateway sends quota headers, Ceidg::rateLimit() returns the limit, the remaining requests and the reset time of the last response. Connection failures and HTTP 5xx answers are retried CEIDG_RETRIES times.

Errors

Situation Result
No entry for a NIP or REGON (HTTP 204) findByNip() / findByRegon() return null, entriesBy*() return []
No entry for an identifier (HTTP 204 or 404) NotFound (identifier, type)
NIP, REGON or identifier malformed InvalidIdentifier, before any request
Token missing InvalidConfiguration
Token rejected (HTTP 401) or without access (HTTP 403) AuthenticationFailed
Request limit exhausted (HTTP 429) RateLimited (retryAfter)
HTTP 400 ApiException with errorCode, for example NIEPOPRAWNY_NUMER_NIP
HTTP 5xx after retries, invalid JSON ApiException (httpStatus, path, isServerError())
Connection failure or timeout after retries ApiException wrapping the ConnectionException
Invalid search filter, date or empty search InvalidArgumentException

AuthenticationFailed and RateLimited extend ApiException; all package exceptions extend Beziworld\Ceidg\Exceptions\CeidgException.

Personal data

CEIDG entries describe natural persons. The register is public, but storing and processing its data in your application is subject to the GDPR: keep only what you need and document the purpose.

Related packages

  • laravel-company-data: GUS REGON (BIR), VAT white list with bank accounts and VIES, without a CEIDG token.
  • laravel-krs: companies, foundations and associations from the National Court Register.
  • laravel-polish-validation: format validation of NIP, REGON, PESEL and more, without an HTTP call.

Development

composer install
composer ci   # pint --test, phpstan (level 8), pest

Tests use fixtures shaped after the responses of the official integrator documentation; no request leaves the test suite.

Author

Created and maintained by Kamil Ćwiertnia, beziworld.eu.

Need an integration with a Polish public register or business API? Get in touch through beziworld.eu.

Other packages by the author

laravel-company-data · laravel-krs · laravel-polish-validation · laravel-uldk

License

MIT. See LICENSE.md. The data comes from CEIDG, published through the data warehouse API (dane.biznes.gov.pl) of the Polish Ministry of Development and Technology (Ministerstwo Rozwoju i Technologii). This package is not affiliated with or endorsed by the Ministry or by biznes.gov.pl.