valsis / ro-company-lookup
Lookup Romanian company data by CUI via ANAF public web services.
Requires
- php: ^8.3
- illuminate/cache: ^12.0
- illuminate/http: ^12.0
- illuminate/support: ^12.0
- spatie/laravel-data: ^4.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0 || ^12.0
- dev-main
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.0
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- dev-dependabot/composer/spatie/laravel-package-tools-1.93.0
- dev-dependabot/composer/laravel/pint-1.27.1
- dev-dependabot/composer/larastan/larastan-3.9.2
- dev-dependabot/composer/phpunit/phpunit-12.5.8
- dev-dependabot/composer/spatie/laravel-data-4.19.1
- dev-release/v0.1.3
- dev-fix/anaf-response-v9-mapping
- dev-release/v0.1.2-changelog
- dev-fix/anaf-endpoint-v9
- dev-feature/try-lookup
- dev-fix/run-tests-check
- dev-fix/ruleset-status-raw
This package is auto-updated.
Last update: 2026-03-23 01:09:11 UTC
README
A production-ready Laravel package that retrieves Romanian company data by CUI from ANAF public web services. It returns clean DTOs, supports batching, caching, retries, circuit breaker, schema audit, and a very simple developer experience.
Requirements
- PHP 8.3+
- Laravel 12+
Compatibility
| PHP | Laravel | CI |
|---|---|---|
| 8.3 | 12.x | |
| 8.5 | 12.x |
Quick start
composer require valsis/ro-company-lookup
use Valsis\RoCompanyLookup\Facades\RoCompanyLookup; return RoCompanyLookup::summaryOrResult('RO12345678');
Example response:
{
"exists": true,
"cui": 12345678,
"name": "EXEMPLU SRL",
"caen": "6201",
"registration_date": "01.01.2020",
"vat_payer": false,
"status": "ok",
"message": null,
"error": null,
"code": null
}
Installation
composer require valsis/ro-company-lookup
Publish the config file (optional):
php artisan vendor:publish --tag=ro-company-lookup-config
Configuration
All configuration lives in config/ro-company-lookup.php.
Core options:
driver: active driver, defaultanaftimezone: default query date timezone, defaultEurope/Bucharestlanguage: output key naming,rooren
Date formatting:
date_output_format: fallback output format for dates (defaultY-m-d)date_output_formats: per-language formats (defaultro => d.m.Y,en => Y-m-d)
HTTP + ANAF:
anaf.base_url,anaf.endpoint,anaf.timeout,anaf.connect_timeoutanaf.retries,anaf.backoff_ms,anaf.user_agent
Cache + resilience:
cache_store,cache_prefix,cache_versioncache_ttl_seconds,stale_ttl_secondsuse_locks,lock_seconds,lock_wait_secondsthrottle_seconds(optional per-CUI guard)
Observability + safety:
logging.enabled,logging.channel,logging.levelschema_audit.enabled,schema_audit.fail_on_unknown,schema_audit.snapshot_pathcircuit_breaker.enabled,circuit_breaker.failure_threshold,circuit_breaker.cooldown_seconds
Raw payloads:
enable_raw(include raw ANAF payloads inmeta.raw)
Usage
Standard lookup
use Valsis\RoCompanyLookup\Facades\RoCompanyLookup; $company = RoCompanyLookup::lookup('RO123456'); $company = RoCompanyLookup::lookup('123456', new DateTimeImmutable('2024-01-10'));
The lookup accepts RO123, ro 123, or 123 and normalizes to an integer CUI. By default, the query date is "today" in Europe/Bucharest.
ANAF mapping is strict to the v9 payload fields (date_generale, inregistrare_scop_Tva, adresa_domiciliu_fiscal, adresa_sediu_social, etc.). Legacy/alternate key names are intentionally not mapped.
Non-throwing API
$result = RoCompanyLookup::tryLookup('RO123456'); if ($result->exists()) { $company = $result->data; }
Summary helpers (simplest UX)
$summary = RoCompanyLookup::summary('RO123456'); $summary = RoCompanyLookup::summaryOrNull('RO123456'); // null if not found $summary = RoCompanyLookup::summaryOrFail('RO123456'); // throws on invalid / not found $summary = RoCompanyLookup::summarySafe('RO123456'); // standard summary payload (never throws) $summary = RoCompanyLookup::summaryOrResult('RO123456'); // same payload, with status/error metadata
Batch helpers
$companies = RoCompanyLookup::batch(['RO123', 'RO456'])->get(); $results = RoCompanyLookup::batch(['RO123', 'BAD', 'RO456'])->tryGet(); $summaries = RoCompanyLookup::batchSummary(['RO1', 'RO2']); $summaries = RoCompanyLookup::batchSummaryWithStatus(['RO1', 'RO2']); $summaries = RoCompanyLookup::batchSummaryMap(['RO1', 'RO2']);
Validation / normalization
$isValid = RoCompanyLookup::isValidCui('RO123456'); $normalized = RoCompanyLookup::normalizeCui(' ro 123456 ');
After normalization, the CUI must be between 2 and 10 digits. Invalid input returns standardized error codes such as invalid_cui, invalid_cui_too_short, or invalid_cui_too_long.
Date formatting (global + per request)
Global (config):
// config/ro-company-lookup.php 'date_output_formats' => [ 'ro' => 'd.m.Y', 'en' => 'Y-m-d', ],
Per request override:
$formatted = RoCompanyLookup::lookupFormatted('RO123456', format: 'd.m.Y', language: 'ro'); $formatted = RoCompanyLookup::tryLookupFormatted('RO123456', format: 'Y-m-d', language: 'en');
DTO structure (high level)
The main response is CompanySimpleData:
company(CUI, names, trade register, profile)caen(primary CAEN)address(fiscal + registered office)contact(phones, emails)legal(current + history)vat(current + history)vat_collection(TVA la incasare)inactive_status(inactiv/reactivat)split_vatmeta(source, query date, cache status, raw)
Company profile (company.profile) includes:
registration_dateregistration_statusfiscal_officeownership_forme_invoice_statuse_invoice_registration_dateiban
Output examples
Summary response
{
"exists": true,
"valid": true,
"status": "ok",
"message": null,
"error": null,
"code": null,
"cui": 12345678,
"name": "EXEMPLU SRL",
"caen": "6201",
"registration_date": "01.01.2020",
"vat_payer": false
}
exists is true only when status is ok. valid indicates whether the CUI input passed validation.
Full response (excerpt, RO)
{
"firma": {
"cui": 12345678,
"nr_reg_com": "J2018000000001",
"denumire": "EXEMPLU SRL",
"profil": {
"data_inregistrare": "01.01.2020",
"stare_inregistrare": "INREGISTRAT din data 01.01.2020"
}
},
"statut_tva": {
"curent": { "cod": 1, "label": "Neplătitor TVA" }
},
"tva_incasare": { "activ": false },
"stare_inactiv": { "este_inactiv": false },
"split_tva": { "activ": false },
"meta": { "sursa": "anaf" }
}
Caching, retries, and resilience
- Results are cached by driver + CUI + date.
- Default cache TTL is 24 hours.
- When a request fails and a stale cache entry exists (within the configured stale TTL), the stale entry is returned with
meta.is_stale = true. - Bump
cache_versionto invalidate cache after mapping changes. - Retries use exponential backoff and only trigger on 429 and 5xx responses.
- Circuit breaker (optional) opens after repeated 5xx responses and cools down for a configurable interval.
- Optional
throttle_secondsguards rapid repeated queries per CUI.
Errors
The package throws typed exceptions for error scenarios:
InvalidCuiExceptionfor invalid inputLookupFailedExceptionfor upstream failuresCircuitOpenExceptionwhen the circuit is open
For user-facing flows, prefer tryLookup() / trySummary() helpers.
Artisan commands
php artisan ro-company-lookup:check 123456 --date=2024-01-10 --raw php artisan ro-company-lookup:demo 123456
Example output (ro-company-lookup:check, fictive data):
{
"adresa": {
"domiciliu_fiscal": {
"formatat": "Str. Exemplu, Nr. 10, Mun. Test, Judet IL",
"judet": "IL",
"cod_judet": "01",
"cod_judet_auto": "IL",
"localitate": "Mun. Test",
"cod_localitate": "999",
"strada": "Str. Exemplu",
"numar": "10",
"cod_postal": "010101",
"detalii": null
},
"sediu_social": {
"formatat": "Str. Exemplu, Nr. 10, Mun. Test, Judet IL",
"judet": "IL",
"cod_judet": "01",
"cod_judet_auto": "IL",
"localitate": "Mun. Test",
"cod_localitate": "999",
"strada": "Str. Exemplu",
"numar": "10",
"cod_postal": "010101",
"detalii": null
}
},
"cod_caen": { "cod": "6201", "label": null, "versiune": null },
"firma": {
"cui": 12345678,
"nr_reg_com": "J2018000000001",
"denumire": "EXEMPLU SRL",
"profil": {
"data_inregistrare": "01.01.2020",
"stare_inregistrare": "INREGISTRAT din data 01.01.2020"
}
},
"statut_tva": {
"curent": { "cod": 1, "label": "Neplătitor TVA", "data_inceput_tva": null }
},
"meta": { "sursa": "anaf", "data_interogare": "01.02.2026", "data_ceruta": "2026-02-01" }
}
Example output (ro-company-lookup:demo, fictive data):
{
"exists": true,
"cui": 12345678,
"name": "EXEMPLU SRL",
"caen": "6201",
"registration_date": "2020-01-01",
"vat_payer": false
}
Schema audit
Enable unknown-key detection and optional snapshots:
'schema_audit' => [ 'enabled' => true, 'fail_on_unknown' => false, 'snapshot_path' => storage_path('logs/anaf-schema'), ],
Documentation
- Full docs:
docs/wiki/Home.md - JSON Schemas:
docs/schemas
Testing
composer test
composer lint
composer analyse
composer ci
Changelog
See CHANGELOG.md.
Contributing
See CONTRIBUTING.md. Dependabot is enabled.
Code of Conduct
See CODE_OF_CONDUCT.md.
Security
See SECURITY.md.
License
The MIT License (MIT). See LICENSE.